Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Instant Search: Add custom taxonomy filtering #13605

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 4 additions & 3 deletions modules/search/instant-search/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -18,16 +19,16 @@ const injectSearchWidget = grabFocus => {
initialFilters={ getFilterQuery() }
initialSort={ getSearchSort() }
initialValue={ getSearchQuery() }
options={ window.JetpackInstantSearchOptions }
options={ window[ SERVER_OBJECT_NAME ] }
/>,
document.body
);
};

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();
Expand Down
105 changes: 53 additions & 52 deletions modules/search/instant-search/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [] ) {
Expand Down Expand Up @@ -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 );
Expand All @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions modules/search/instant-search/lib/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const SERVER_OBJECT_NAME = 'JetpackInstantSearchOptions';
gibrown marked this conversation as resolved.
Show resolved Hide resolved
59 changes: 43 additions & 16 deletions modules/search/instant-search/lib/query-string.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
* External dependencies
*/
import { decode, encode } from 'qss';
// NOTE: We only import the debounce package here for to reduced bundle size.
jsnmoon marked this conversation as resolved.
Show resolved Hide resolved
// 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 ) );
Expand Down Expand Up @@ -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 ) {
Expand Down