From 0c592cd207f1fea79ed95e22236e11c0d0a0ae30 Mon Sep 17 00:00:00 2001 From: Jason Moon Date: Fri, 27 Sep 2019 16:25:18 -0600 Subject: [PATCH 1/3] Add internal dependencies docblock --- .../instant-search/components/search-filter-post-types.jsx | 4 ++++ .../instant-search/components/search-filter-taxonomies.jsx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/modules/search/instant-search/components/search-filter-post-types.jsx b/modules/search/instant-search/components/search-filter-post-types.jsx index 7a7bdde18f58f..57b8e4881defc 100644 --- a/modules/search/instant-search/components/search-filter-post-types.jsx +++ b/modules/search/instant-search/components/search-filter-post-types.jsx @@ -5,6 +5,10 @@ */ import { h, createRef, Component } from 'preact'; import strip from 'strip'; + +/** + * Internal dependencies + */ import { getCheckedInputNames } from '../lib/dom'; export default class SearchFilterPostTypes extends Component { diff --git a/modules/search/instant-search/components/search-filter-taxonomies.jsx b/modules/search/instant-search/components/search-filter-taxonomies.jsx index 5afd3d2ac6f96..7bd3946863f08 100644 --- a/modules/search/instant-search/components/search-filter-taxonomies.jsx +++ b/modules/search/instant-search/components/search-filter-taxonomies.jsx @@ -5,6 +5,10 @@ */ import { h, createRef, Component } from 'preact'; import strip from 'strip'; + +/** + * Internal dependencies + */ import { getCheckedInputNames } from '../lib/dom'; export default class SearchFilterTaxonomies extends Component { From 88a5d001ec1b67bcc0db574d931275bb18dd7836 Mon Sep 17 00:00:00 2001 From: Jason Moon Date: Fri, 27 Sep 2019 16:27:35 -0600 Subject: [PATCH 2/3] Add support for custom taxonomy filtering This refactors how we handle query strings, which affects our API logic. --- modules/search/instant-search/index.jsx | 7 +- modules/search/instant-search/lib/api.js | 105 +++++++++--------- .../search/instant-search/lib/constants.js | 1 + .../search/instant-search/lib/query-string.js | 59 +++++++--- 4 files changed, 101 insertions(+), 71 deletions(-) create mode 100644 modules/search/instant-search/lib/constants.js diff --git a/modules/search/instant-search/index.jsx b/modules/search/instant-search/index.jsx index 109b9fbe8338e..f545761b7fc7e 100644 --- a/modules/search/instant-search/index.jsx +++ b/modules/search/instant-search/index.jsx @@ -10,6 +10,7 @@ import { h, render } from 'preact'; */ import SearchWidget from './components/search-widget'; import { getSearchQuery, getFilterQuery, getSearchSort } from './lib/query-string'; +import { SERVER_OBJECT_NAME } from './lib/constants'; const injectSearchWidget = grabFocus => { render( @@ -18,7 +19,7 @@ const injectSearchWidget = grabFocus => { initialFilters={ getFilterQuery() } initialSort={ getSearchSort() } initialValue={ getSearchQuery() } - options={ window.JetpackInstantSearchOptions } + options={ window[ SERVER_OBJECT_NAME ] } />, document.body ); @@ -26,8 +27,8 @@ const injectSearchWidget = grabFocus => { document.addEventListener( 'DOMContentLoaded', function() { if ( - !! window.JetpackInstantSearchOptions && - 'siteId' in window.JetpackInstantSearchOptions && + !! window[ SERVER_OBJECT_NAME ] && + 'siteId' in window[ SERVER_OBJECT_NAME ] && document.body.classList.contains( 'search' ) ) { injectSearchWidget(); diff --git a/modules/search/instant-search/lib/api.js b/modules/search/instant-search/lib/api.js index d1bce4364a49f..c04c1f9b84519 100644 --- a/modules/search/instant-search/lib/api.js +++ b/modules/search/instant-search/lib/api.js @@ -5,6 +5,11 @@ import fetch from 'unfetch'; import { encode } from 'qss'; import { flatten } from 'q-flat'; +/** + * Internal dependencies + */ +import { getFilterKeys } from './query-string'; + const isLengthyArray = array => Array.isArray( array ) && array.length > 0; export function buildFilterAggregations( widgets = [] ) { @@ -40,10 +45,7 @@ export function buildFilterAggregations( widgets = [] ) { } const DATE_REGEX = /(\d{4})-(\d{2})-(\d{2})/; -function generateDateRange( query, type ) { - // NOTE: This only supports a single date query at this time - const input = Array.isArray( query ) && query[ 0 ]; - +function generateDateRangeFilter( fieldName, input, type ) { let year, month; if ( type === 'year' ) { [ , year, , ] = input.match( DATE_REGEX ); @@ -52,67 +54,66 @@ function generateDateRange( query, type ) { if ( type === 'month' ) { [ , year, month ] = input.match( DATE_REGEX ); } - + let startDate = ''; + let endDate = ''; if ( month ) { - return { startDate: `${ year }-${ month }-01`, endDate: `${ year }-${ +month + 1 }-01` }; + startDate = `${ year }-${ month }-01`; + endDate = `${ year }-${ +month + 1 }-01`; } if ( year ) { - return { startDate: `${ year }-01-01`, endDate: `${ +year + 1 }-01-01` }; + startDate = `${ year }-01-01`; + endDate = `${ +year + 1 }-01-01`; } - return { startDate: '', endDate: '' }; + + return { range: { [ fieldName ]: { gte: startDate, lt: endDate } } }; } +const filterKeyToEsFilter = new Map( [ + // Post type + [ 'post_types', postType => ( { term: { post_type: postType } } ) ], + + // Built-in taxonomies + [ 'category', category => ( { term: { 'category.slug': category } } ) ], + [ 'post_tag', tag => ( { term: { 'tag.slug': tag } } ) ], + + // Dates + [ 'month_post_date', datestring => generateDateRangeFilter( 'date', datestring, 'month' ) ], + [ + 'month_post_date_gmt', + datestring => generateDateRangeFilter( 'date_gmt', datestring, 'month' ), + ], + [ 'month_post_modified', datestring => generateDateRangeFilter( 'date', datestring, 'month' ) ], + [ + 'month_post_modified_gmt', + datestring => generateDateRangeFilter( 'date_gmt', datestring, 'month' ), + ], + [ 'year_post_date', datestring => generateDateRangeFilter( 'date', datestring, 'year' ) ], + [ 'year_post_date_gmt', datestring => generateDateRangeFilter( 'date_gmt', datestring, 'year' ) ], + [ 'year_post_modified', datestring => generateDateRangeFilter( 'date', datestring, 'year' ) ], + [ + 'year_post_modified_gmt', + datestring => generateDateRangeFilter( 'date_gmt', datestring, 'year' ), + ], +] ); + function buildFilterObject( filterQuery ) { if ( ! filterQuery ) { return {}; } const filter = { bool: { must: [] } }; - if ( isLengthyArray( filterQuery.post_types ) ) { - filterQuery.post_types.forEach( postType => { - filter.bool.must.push( { term: { post_type: postType } } ); - } ); - } - if ( isLengthyArray( filterQuery.post_tag ) ) { - filterQuery.post_tag.forEach( tag => { - filter.bool.must.push( { term: { 'tag.slug': tag } } ); + getFilterKeys() + .filter( key => isLengthyArray( filterQuery[ key ] ) ) + .forEach( key => { + filterQuery[ key ].forEach( item => { + if ( filterKeyToEsFilter.has( key ) ) { + filter.bool.must.push( filterKeyToEsFilter.get( key )( item ) ); + } else { + // If key is not in the standard map, assume to be a custom taxonomy + filter.bool.must.push( { term: { [ `taxonomy.${ key }.slug` ]: item } } ); + } + } ); } ); - } - if ( isLengthyArray( filterQuery.month_post_date ) ) { - const { startDate, endDate } = generateDateRange( filterQuery.month_post_date, 'month' ); - filter.bool.must.push( { range: { date: { gte: startDate, lt: endDate } } } ); - } - if ( isLengthyArray( filterQuery.month_post_date_gmt ) ) { - const { startDate, endDate } = generateDateRange( filterQuery.month_post_date_gmt, 'month' ); - filter.bool.must.push( { range: { date_gmt: { gte: startDate, lt: endDate } } } ); - } - if ( isLengthyArray( filterQuery.month_post_modified ) ) { - const { startDate, endDate } = generateDateRange( filterQuery.month_post_modified, 'month' ); - filter.bool.must.push( { range: { modified: { gte: startDate, lt: endDate } } } ); - } - if ( isLengthyArray( filterQuery.month_post_modified_gmt ) ) { - const { startDate, endDate } = generateDateRange( - filterQuery.month_post_modified_gmt, - 'month' - ); - filter.bool.must.push( { range: { modified_gmt: { gte: startDate, lt: endDate } } } ); - } - if ( isLengthyArray( filterQuery.year_post_date ) ) { - const { startDate, endDate } = generateDateRange( filterQuery.year_post_date, 'year' ); - filter.bool.must.push( { range: { date: { gte: startDate, lt: endDate } } } ); - } - if ( isLengthyArray( filterQuery.year_post_date_gmt ) ) { - const { startDate, endDate } = generateDateRange( filterQuery.year_post_date_gmt, 'year' ); - filter.bool.must.push( { range: { date_gmt: { gte: startDate, lt: endDate } } } ); - } - if ( isLengthyArray( filterQuery.year_post_modified ) ) { - const { startDate, endDate } = generateDateRange( filterQuery.year_post_modified, 'year' ); - filter.bool.must.push( { range: { modified: { gte: startDate, lt: endDate } } } ); - } - if ( isLengthyArray( filterQuery.year_post_modified_gmt ) ) { - const { startDate, endDate } = generateDateRange( filterQuery.year_post_modified_gmt, 'year' ); - filter.bool.must.push( { range: { modified_gmt: { gte: startDate, lt: endDate } } } ); - } return filter; } diff --git a/modules/search/instant-search/lib/constants.js b/modules/search/instant-search/lib/constants.js new file mode 100644 index 0000000000000..47ad6bc4933ab --- /dev/null +++ b/modules/search/instant-search/lib/constants.js @@ -0,0 +1 @@ +export const SERVER_OBJECT_NAME = 'JetpackInstantSearchOptions'; diff --git a/modules/search/instant-search/lib/query-string.js b/modules/search/instant-search/lib/query-string.js index d0935651558ca..82d77240eaeba 100644 --- a/modules/search/instant-search/lib/query-string.js +++ b/modules/search/instant-search/lib/query-string.js @@ -2,6 +2,15 @@ * External dependencies */ import { decode, encode } from 'qss'; +// NOTE: We only import the debounce package here for to reduced bundle size. +// Do not import the entire lodash library! +// eslint-disable-next-line lodash/import-scope +import get from 'lodash/get'; + +/** + * Internal dependencies + */ +import { SERVER_OBJECT_NAME } from './constants'; function getQuery() { return decode( window.location.search.substring( 1 ) ); @@ -83,27 +92,45 @@ function getFilterQueryByKey( filterKey ) { return query[ filterKey ]; } +export function getFilterKeys() { + const keys = [ + // Post types + 'post_types', + // Date filters + 'month_post_date', + 'month_post_date_gmt', + 'month_post_modified', + 'month_post_modified_gmt', + 'year_post_date', + 'year_post_date_gmt', + 'year_post_modified', + 'year_post_modified_gmt', + ]; + + // Extract taxonomy names from server widget data + const widgetFilters = get( window[ SERVER_OBJECT_NAME ], 'widgets[0].filters' ); + if ( widgetFilters ) { + return [ + ...keys, + ...widgetFilters + .filter( filter => filter.type === 'taxonomy' ) + .map( filter => filter.taxonomy ), + ]; + } + return [ ...keys, 'category', 'post_tag' ]; +} + export function getFilterQuery( filterKey ) { if ( filterKey ) { return getFilterQueryByKey( filterKey ); } - return { - // Taxonomies - category: getFilterQueryByKey( 'category' ), - post_tag: getFilterQueryByKey( 'post_tag' ), - // Post types - post_types: getFilterQueryByKey( 'post_types' ), - // Date filters - month_post_date: getFilterQueryByKey( 'month_post_date' ), - month_post_date_gmt: getFilterQueryByKey( 'month_post_date_gmt' ), - month_post_modified: getFilterQueryByKey( 'month_post_modified' ), - month_post_modified_gmt: getFilterQueryByKey( 'month_post_modified_gmt' ), - year_post_date: getFilterQueryByKey( 'year_post_date' ), - year_post_date_gmt: getFilterQueryByKey( 'year_post_date_gmt' ), - year_post_modified: getFilterQueryByKey( 'year_post_modified' ), - year_post_modified_gmt: getFilterQueryByKey( 'year_post_modified_gmt' ), - }; + return Object.assign( + {}, + ...getFilterKeys().map( key => ( { + [ key ]: getFilterQueryByKey( key ), + } ) ) + ); } export function setFilterQuery( filterKey, filterValue ) { From 4c0540a9d03acd49c1a0a878392bf8d8958cd5b1 Mon Sep 17 00:00:00 2001 From: Jason Moon Date: Tue, 1 Oct 2019 13:28:26 -0600 Subject: [PATCH 3/3] Fix incorrect wording in NOTE comment --- .../search/instant-search/components/search-filters-widget.jsx | 2 +- modules/search/instant-search/lib/query-string.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/search/instant-search/components/search-filters-widget.jsx b/modules/search/instant-search/components/search-filters-widget.jsx index c561dcdabf5e7..16d417fefa640 100644 --- a/modules/search/instant-search/components/search-filters-widget.jsx +++ b/modules/search/instant-search/components/search-filters-widget.jsx @@ -4,7 +4,7 @@ * External dependencies */ import { h, Component } from 'preact'; -// NOTE: We only import the debounce package here for to reduced bundle size. +// NOTE: We only import the get package here for to reduced bundle size. // Do not import the entire lodash library! // eslint-disable-next-line lodash/import-scope import get from 'lodash/get'; diff --git a/modules/search/instant-search/lib/query-string.js b/modules/search/instant-search/lib/query-string.js index 82d77240eaeba..3c64b7c45f411 100644 --- a/modules/search/instant-search/lib/query-string.js +++ b/modules/search/instant-search/lib/query-string.js @@ -2,7 +2,7 @@ * External dependencies */ import { decode, encode } from 'qss'; -// NOTE: We only import the debounce package here for to reduced bundle size. +// NOTE: We only import the get package here for to reduced bundle size. // Do not import the entire lodash library! // eslint-disable-next-line lodash/import-scope import get from 'lodash/get';