From 41df8336f84e77b5fca95a25f69a7cfa86f43692 Mon Sep 17 00:00:00 2001 From: razvanMiu Date: Thu, 14 Jan 2021 09:42:32 +0200 Subject: [PATCH] Clean up --- src/components/manage/Blocks/DataBase/DB.jsx | 88 +++++ .../DiscodataComponents/Custom/View.jsx | 4 +- .../Blocks/DiscodataComponentsBlock/Edit.jsx | 6 +- .../Blocks/DiscodataComponentsBlock/View.jsx | 6 +- .../Blocks/DiscodataSqlBuilder/Edit.jsx | 303 ++++++++++++++++++ .../Blocks/DiscodataSqlBuilder/View.jsx | 155 +++++++++ .../Blocks/DiscodataTableBlock/Edit.jsx | 6 +- .../Blocks/DiscodataTableBlock/View.jsx | 6 +- .../manage/Blocks/PollutantIndex/View.jsx | 9 +- .../manage/Blocks/SiteBlocks/QueryBuilder.jsx | 20 +- .../volto/components/theme/Search/Search.jsx | 4 +- src/localconfig.js | 16 +- 12 files changed, 583 insertions(+), 40 deletions(-) create mode 100644 src/components/manage/Blocks/DataBase/DB.jsx create mode 100644 src/components/manage/Blocks/DiscodataSqlBuilder/Edit.jsx create mode 100644 src/components/manage/Blocks/DiscodataSqlBuilder/View.jsx diff --git a/src/components/manage/Blocks/DataBase/DB.jsx b/src/components/manage/Blocks/DataBase/DB.jsx new file mode 100644 index 00000000..080ff1ac --- /dev/null +++ b/src/components/manage/Blocks/DataBase/DB.jsx @@ -0,0 +1,88 @@ +const collation = { + _: ' ', + latin_ci_ai: ' COLLATE Latin1_General_CI_AI ', + latin_ci_as: ' COLLATE Latin1_General_CI_AS ', +}; + +class DB { + static table(query, path = '', pagination = {}) { + return new Table(query, path, pagination); + } +} + +class Table { + constructor(query, path, pagination) { + this.query = query; + this.path = path || '/'; + this.pagination = pagination || {}; + } + get() { + const { p, nrOfHits } = this.pagination; + return `${this.path}?query=${this.query}${ + typeof p !== 'undefined' ? '&p=' + p : '' + }${typeof nrOfHits !== 'undefined' ? '&nrOfHits=' + nrOfHits : ''}`; + } + encode() { + this.query = encodeURI(this.query); + return this; + } + where(whereStatements, additionalWhereStatements) { + let queryString = ''; + let additionalString = ''; + if (additionalWhereStatements.length > 0) { + additionalString = additionalWhereStatements.join(' AND '); + } + if (whereStatements?.length > 0) { + const whereString = whereStatements + .map((where, index) => { + let whereString = ''; + if (Array.isArray(where.value) && !where.value.length) return null; + if (typeof where.value === 'string' && !where.value.length) + return null; + if (Array.isArray(where.value)) { + const baseSql = `(${where.discodataKey + .split('.') + .map((item) => '[' + item + ']') + .join('.')}${collation[where.collation || '_']}LIKE '${ + where.isExact ? '' : '%' + }:option${where.isExact ? '' : '%'}')`; + return `(${where.value + .map((option) => baseSql.replace(':option', option)) + .join(' OR ')})`; + } else { + whereString = `${where.discodataKey + .split('.') + .map((item) => '[' + item + ']') + .join('.')}${collation[where.collation || '_']}LIKE '${ + where.regex && typeof where.regex === 'string' + ? where.regex.replace(':value', where.value) + : where.value + }'`; + } + return whereString; + }) + .filter((value) => value) + .join(' AND '); + if (whereString.length) { + queryString += ` WHERE ${whereString}${ + additionalString ? ` AND ${additionalString}` : '' + }`; + } else if (additionalString.length > 0) { + queryString += ` WHERE ${additionalString}`; + } + } else if (additionalWhereStatements.length > 0) { + queryString += ` WHERE ${additionalString}`; + } + if (this.query.includes(':where')) { + this.query = this.query.replace( + ':where', + queryString.replace(' WHERE ', ''), + ); + } else { + this.query += queryString; + } + return this; + } +} + +export default DB; diff --git a/src/components/manage/Blocks/DiscodataComponents/Custom/View.jsx b/src/components/manage/Blocks/DiscodataComponents/Custom/View.jsx index 2a8e64c7..b85aef99 100644 --- a/src/components/manage/Blocks/DiscodataComponents/Custom/View.jsx +++ b/src/components/manage/Blocks/DiscodataComponents/Custom/View.jsx @@ -5,7 +5,7 @@ import { connect } from 'react-redux'; import { isArray } from 'lodash'; import { Dropdown, Header } from 'semantic-ui-react'; import { setQueryParam } from 'volto-datablocks/actions'; -import DiscodataSqlBuilder from 'volto-datablocks/DiscodataSqlBuilder/View'; +import { ViewDiscodataSqlBuilder } from 'volto-datablocks/components'; import ReactTooltip from 'react-tooltip'; import Icon from '@plone/volto/components/theme/Icon/Icon'; import moment from 'moment'; @@ -290,7 +290,7 @@ const CountrySelector = (props) => { return (
- keys && keys.map((k) => [k, k]); @@ -433,14 +433,14 @@ const Edit = (props) => { /* eslint-disable-next-line */ }, [props.data, props.discodata_resources, props.discodata_query.search]); return ( -

Discodata components - edit mode

-
+ ); }; diff --git a/src/components/manage/Blocks/DiscodataComponentsBlock/View.jsx b/src/components/manage/Blocks/DiscodataComponentsBlock/View.jsx index e83c4696..b8a512fc 100644 --- a/src/components/manage/Blocks/DiscodataComponentsBlock/View.jsx +++ b/src/components/manage/Blocks/DiscodataComponentsBlock/View.jsx @@ -8,7 +8,7 @@ import { arrayToTree } from 'performant-array-to-tree'; import qs from 'query-string'; import { Table, Dropdown, List, Header } from 'semantic-ui-react'; import './style.css'; -import DiscodataSqlBuilderView from 'volto-datablocks/DiscodataSqlBuilder/View'; +import { ViewDiscodataSqlBuilder } from 'volto-datablocks/components'; import { setQueryParam, deleteQueryParam } from 'volto-datablocks/actions'; import cx from 'classnames'; import Icon from '@plone/volto/components/theme/Icon/Icon'; @@ -718,7 +718,7 @@ const View = (props) => { let root = arrayToTree(componentsArray); if (!__CLIENT__) return ''; return ( - +
{(state.selectedResource && @@ -735,7 +735,7 @@ const View = (props) => { (props.data.mode === 'edit' ?

Add components

: '')}
-
+ ); }; diff --git a/src/components/manage/Blocks/DiscodataSqlBuilder/Edit.jsx b/src/components/manage/Blocks/DiscodataSqlBuilder/Edit.jsx new file mode 100644 index 00000000..0a0a26bf --- /dev/null +++ b/src/components/manage/Blocks/DiscodataSqlBuilder/Edit.jsx @@ -0,0 +1,303 @@ +import React, { useState, useEffect } from 'react'; +import { connect } from 'react-redux'; +import { compose } from 'redux'; +import _uniqueId from 'lodash/uniqueId'; +import qs from 'query-string'; +import RenderFields from '../Utils/RenderFields'; +import { settings } from '~/config'; + +import View from './View'; + +const makeChoices = (keys) => keys && keys.map((k) => [k, k]); + +const getSchema = (props) => { + if (!props) return {}; + const { query } = props; + const { search } = props.discodata_query; + const sqls = props.data?.sql?.value + ? JSON.parse(props.data.sql.value).properties + : {}; + const whereStatements = props.data?.where?.value + ? JSON.parse(props.data.where.value).properties + : {}; + const providerUrlDescription = {}; + const globalQuery = { ...query, ...search }; + sqls && + Object.entries(sqls).forEach(([sqlKey, sqlValue]) => { + let whereArray = []; + providerUrlDescription[sqlKey] = sqlValue.sql; + Object.entries(whereStatements).forEach(([whereKey, whereValue]) => { + if (whereValue.sqlId === sqlKey) { + whereArray.push( + `WHERE ${whereValue.key} LIKE ${ + query?.[whereValue.queryParam] || '' + }`, + ); + } + }); + if (whereArray.length) { + providerUrlDescription[sqlKey] += ' ' + whereArray.join(' AND '); + } + }); + return { + importExport: { + title: 'Import/Export block data', + type: 'import-export', + }, + provider_url: { + title: 'Provider url', + type: 'text', + defaultValue: props.providerUrl, + description: Object.entries(providerUrlDescription).map( + ([sql, value]) => ( + + + {value} + + + ), + ), + }, + sql: { + title: 'SQL', + type: 'schema', + fieldSetTitle: 'SQL metadata', + fieldSetId: 'sql_metadata', + fieldSetSchema: { + fieldsets: [ + { + id: 'default', + title: 'title', + fields: [ + 'title', + 'id', + 'isCollection', + 'hasPagination', + 'urlQuery', + 'sql', + 'packageName', + ], + }, + ], + properties: { + title: { + title: 'Title', + type: 'text', + }, + id: { + title: 'Id', + type: 'text', + }, + isCollection: { + title: 'Collection', + type: 'boolean', + defaultValue: false, + }, + hasPagination: { + title: 'Pagination', + type: 'boolean', + defaultValue: true, + disabled: (formData) => !formData.isCollection, + }, + urlQuery: { + title: 'Use Query from url', + type: 'boolean', + defaultValue: true, + disabled: (formData) => formData.isCollection, + }, + sql: { + title: 'SQL', + widget: 'textarea', + }, + packageName: { + title: 'Package discodata key name', + type: (formData) => (!formData.urlQuery ? 'text' : 'array'), + choices: (formData) => { + if (!formData.urlQuery) return undefined; + return globalQuery ? makeChoices(Object.keys(globalQuery)) : []; + }, + disabled: (formData) => formData.isCollection, + }, + }, + required: (formData) => { + if (!formData.isCollection) + return ['title', 'id', 'sql', 'packageName']; + return ['title', 'id', 'sql']; + }, + }, + editFieldset: false, + deleteFieldset: false, + }, + where: { + title: 'Where statements', + type: 'schema', + fieldSetTitle: 'Where statements metadata', + fieldSetId: 'where_statements_metadata', + fieldSetSchema: { + fieldsets: [ + { + id: 'default', + title: 'title', + fields: [ + 'title', + 'id', + 'sqlId', + 'isExact', + 'urlQuery', + 'key', + 'queryParam', + 'regex', + 'collation', + ], + }, + ], + properties: { + title: { + title: 'Title', + type: 'text', + }, + id: { + title: 'Id', + type: 'text', + }, + sqlId: { + title: 'For sql', + type: 'array', + choices: sqls ? makeChoices(Object.keys(sqls)) : [], + }, + isExact: { + title: 'Is exact', + type: 'boolean', + defaultValue: false, + }, + urlQuery: { + title: 'Use Query from url', + type: 'boolean', + defaultValue: true, + }, + key: { + title: 'Query to set', + type: (formData) => (!formData.urlQuery ? 'text' : 'array'), + choices: (formData) => { + if (!formData.urlQuery) return undefined; + return globalQuery ? makeChoices(Object.keys(globalQuery)) : []; + }, + }, + queryParam: { + title: 'Query to use', + type: (formData) => (!formData.urlQuery ? 'text' : 'array'), + choices: (formData) => { + if (!formData.urlQuery) return undefined; + return globalQuery ? makeChoices(Object.keys(globalQuery)) : []; + }, + }, + regex: { + title: 'Regex', + type: 'array', + choices: [ + ['%:value%', '%Value%'], + [':value%', 'Value%'], + ['%:value', '%Value'], + ], + }, + collation: { + title: 'Collaltion', + type: 'array', + choices: [ + ['_', 'No value'], + ['latin_ci_ai', 'Latin_CI_AI'], + ['latin_ci_as', 'Latin_CI_AS'], + ], + }, + }, + required: ['id', 'title', 'sqlId', 'key', 'queryParam'], + }, + editFieldset: false, + deleteFieldset: false, + }, + groupBy: { + title: 'Group by statements', + type: 'schema', + fieldSetTitle: 'Group by statements metadata', + fieldSetId: 'group_by_statements_metadata', + fieldSetSchema: { + fieldsets: [ + { + id: 'default', + title: 'title', + fields: ['title', 'id', 'sqlId', 'discodataKey', 'key'], + }, + ], + properties: { + title: { + title: 'Title', + type: 'text', + }, + id: { + title: 'Id', + type: 'text', + }, + sqlId: { + title: 'For sql', + type: 'array', + choices: sqls ? makeChoices(Object.keys(sqls)) : [], + }, + discodataKey: { + title: 'Discodata query param', + type: 'text', + }, + key: { + title: 'Resource package key', + type: 'text', + }, + }, + required: ['id', 'title', 'sqlId', 'discodataKey', 'key'], + }, + editFieldset: false, + deleteFieldset: false, + }, + ...props.optionalSchema, + }; +}; + +const Edit = (props) => { + const [state, setState] = useState({ + schema: getSchema({ ...props, providerUrl: settings.providerUrl }), + id: _uniqueId('block_'), + }); + useEffect(() => { + setState({ + ...state, + schema: getSchema({ + ...props, + providerUrl: settings.providerUrl, + }), + }); + /* eslint-disable-next-line */ + }, [ + props.data, + props.discodata_resources, + props.discodata_query.search, + props.optionalSchema, + ]); + return ( + //
+ <> + + {props.children || } + + ); +}; + +export default compose( + connect((state, props) => ({ + query: qs.parse(state.router.location.search), + pathname: state.router.location.pathname, + discodata_resources: state.discodata_resources, + discodata_query: state.discodata_query, + })), +)(Edit); diff --git a/src/components/manage/Blocks/DiscodataSqlBuilder/View.jsx b/src/components/manage/Blocks/DiscodataSqlBuilder/View.jsx new file mode 100644 index 00000000..14bf1a54 --- /dev/null +++ b/src/components/manage/Blocks/DiscodataSqlBuilder/View.jsx @@ -0,0 +1,155 @@ +import React, { useState, useEffect } from 'react'; +import { connect } from 'react-redux'; +import { compose } from 'redux'; +import qs from 'query-string'; +import DB from '../DataBase/DB'; +import { settings } from '~/config'; +import { getDiscodataResource } from '../actions'; +const ViewWrapper = (props) => { + const [state, setState] = useState({ + mounted: false, + }); + const { query } = props; + const { search } = props.discodata_query; + const { pendingRequests, error } = props.discodata_resources; + /* ========================= */ + const sqls = props.data?.sql?.value + ? JSON.parse(props.data.sql.value).properties + : {}; + const where = props.data?.where?.value + ? JSON.parse(props.data.where.value).properties + : {}; + const groupBy = props.data?.groupBy?.value + ? JSON.parse(props.data.groupBy.value).properties + : {}; + /* ========================= */ + const globalQuery = { ...query, ...search }; + const additionalWhereStatements = props.additionalWhereStatements || []; + useEffect(() => { + setState({ ...state, mounted: true }); + /* eslint-disable-next-line */ + }, []); + useEffect(() => { + if (state.mounted) { + Object.entries(sqls).forEach(([sqlKey, sqlValue]) => { + const isCollection = sqlValue.isCollection; + const hasPagination = sqlValue.hasPagination; + let requestsMetadataDiff = false; + let whereStatements = [], + groupByStatements = []; + whereStatements = Object.keys(where) + .filter((key) => { + return ( + where[key].sqlId === sqlKey && + globalQuery[where[key].queryParam] && + where[key].key + ); + }) + .map((key) => { + return { + discodataKey: where[key].key, + value: Array.isArray(globalQuery[where[key].queryParam]) + ? [ + ...globalQuery[where[key].queryParam].filter( + (query) => query, + ), + ] + : globalQuery[where[key].queryParam], + regex: where[key].regex || null, + isExact: where[key].isExact || false, + collation: where[key].collation || '_', + }; + }); + const url = DB.table( + sqlValue.sql, + settings.providerUrl, + hasPagination ? props.pagination : {}, + ) + .where(whereStatements, additionalWhereStatements) + .encode() + .get(); + const request = { + url, + isCollection, + resourceKey: sqlKey || '', + requestsMetadata: {}, + }; + if (!isCollection) { + groupByStatements = Object.keys(groupBy) + .filter((key) => { + return groupBy[key].sqlId === sqlKey; + }) + .map((key) => { + return { + discodataKey: groupBy[key].discodataKey, + key: groupBy[key].key, + }; + }); + /* Update requestsMetadata */ + request.requestsMetadata.where = [...whereStatements]; + request.requestsMetadata.groupBy = [...groupByStatements]; + request.requestsMetadata.query = + globalQuery[sqlValue.packageName] || ''; + if ( + JSON.stringify(request.requestsMetadata) !== + JSON.stringify( + props.discodata_resources.requestsMetadata[ + `${sqlKey}_${globalQuery[sqlValue.packageName]}` + ], + ) && + whereStatements.length > 0 + ) { + requestsMetadataDiff = true; + request.search = globalQuery || {}; + request.groupBy = groupByStatements || []; + request.key = sqlValue.packageName || ''; + } + } else { + request.requestsMetadata.where = [ + ...whereStatements, + ...additionalWhereStatements, + ]; + request.requestsMetadata.pagination = { ...hasPagination } + ? props.pagination || { p: 1, nrOfHits: 5 } + : null; + if ( + JSON.stringify(request.requestsMetadata) !== + JSON.stringify(props.discodata_resources.requestsMetadata[sqlKey]) + ) { + requestsMetadataDiff = true; + } + } + if ( + // !done && + ((!isCollection && + !pendingRequests[ + `${sqlKey}_${globalQuery[sqlValue.packageName]}` + ] && + !error[`${sqlKey}_${globalQuery[sqlValue.packageName]}`]) || + (isCollection && !pendingRequests[sqlKey] && !error[sqlKey])) && + requestsMetadataDiff + ) { + props.getDiscodataResource(request); + } + }); + } + /* eslint-disable-next-line */ + }, [sqls, where, groupBy, search]); + return ( + <> + {props.mode === 'edit-no-children' ? 'SQL BUILDER BLOCK' : props.children} + + ); +}; + +export default compose( + connect( + (state, props) => ({ + query: qs.parse(state.router.location.search), + pathname: state.router.location.pathname, + discodata_resources: state.discodata_resources, + discodata_query: state.discodata_query, + }), + { getDiscodataResource }, + ), +)(ViewWrapper); diff --git a/src/components/manage/Blocks/DiscodataTableBlock/Edit.jsx b/src/components/manage/Blocks/DiscodataTableBlock/Edit.jsx index a5fbf7e0..0919f701 100644 --- a/src/components/manage/Blocks/DiscodataTableBlock/Edit.jsx +++ b/src/components/manage/Blocks/DiscodataTableBlock/Edit.jsx @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import { compose } from 'redux'; import { injectIntl } from 'react-intl'; import View from './View'; -import DiscodataSqlBuilderEdit from 'volto-datablocks/DiscodataSqlBuilder/Edit'; +import { EditDiscodataSqlBuilder } from 'volto-datablocks/components'; const schema = { itemsCountKey: { @@ -158,13 +158,13 @@ const Edit = React.forwardRef((props) => { } return ( - - + ); }); diff --git a/src/components/manage/Blocks/DiscodataTableBlock/View.jsx b/src/components/manage/Blocks/DiscodataTableBlock/View.jsx index 7b4b0e85..4a8dceba 100644 --- a/src/components/manage/Blocks/DiscodataTableBlock/View.jsx +++ b/src/components/manage/Blocks/DiscodataTableBlock/View.jsx @@ -8,7 +8,7 @@ import { Table, Pagination } from 'semantic-ui-react'; import downSVG from '@plone/volto/icons/down-key.svg'; import upSVG from '@plone/volto/icons/up-key.svg'; import { Icon } from '@plone/volto/components'; -import DiscodataSqlBuilderView from 'volto-datablocks/DiscodataSqlBuilder/View'; +import { ViewDiscodataSqlBuilder } from 'volto-datablocks/components'; import { setQueryParam } from 'volto-datablocks/actions'; import { Dimmer, Loader } from 'semantic-ui-react'; import LinearProgress from '@material/react-linear-progress'; @@ -187,7 +187,7 @@ const View = (props) => { return (
- { ) : (
)} - + {!items?.length ? ( European Environment Agency diff --git a/src/components/manage/Blocks/PollutantIndex/View.jsx b/src/components/manage/Blocks/PollutantIndex/View.jsx index 70071cff..cf18abc9 100644 --- a/src/components/manage/Blocks/PollutantIndex/View.jsx +++ b/src/components/manage/Blocks/PollutantIndex/View.jsx @@ -2,11 +2,8 @@ import React, { useState, useEffect, useRef } from 'react'; import { compose } from 'redux'; import { connect } from 'react-redux'; import { Tab, Dropdown, Table } from 'semantic-ui-react'; -import DiscodataSqlBuilder from 'volto-datablocks/DiscodataSqlBuilder/View'; -import { - setQueryParam, - deleteQueryParam, -} from 'volto-datablocks/actions'; +import { ViewDiscodataSqlBuilder } from 'volto-datablocks/components'; +import { setQueryParam, deleteQueryParam } from 'volto-datablocks/actions'; import cx from 'classnames'; import './style.css'; @@ -802,7 +799,7 @@ const View = (props) => { return (
- { @@ -10,7 +10,7 @@ const QueryBuilder = (props) => { HEADER DB: Browse5_Header, site_flags */} - { SITE DETAILS (POLLUTANTS) DB: Browse6Header */} - { FACILITY DETAILS (POLLUTANTS) DB: Browse7Header */} - { SITE DETAILS 3 DB: Browse8Header */} - { Permits DB: Browse9Header_Permit */} - { BAT Conclusions DB: Browse9Header_BATConclusion */} - { BAT Derogations DB: Browse9Header_BATDerogation */} - { LCP DETAILS DB: Browse10_Header */} - -