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

pagination: Fix disappearing pagination on back nav and refresh #1060

Merged
merged 6 commits into from
Sep 4, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 11 additions & 1 deletion src/answers-umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import GlobalStorage from './core/storage/globalstorage';
import { AnswersComponentError } from './core/errors/errors';
import AnalyticsEvent from './core/analytics/analyticsevent';
import StorageKeys from './core/storage/storagekeys';
import QueryTriggers from './core/models/querytriggers';
import SearchConfig from './core/models/searchconfig';
import AutoCompleteApi from './core/search/autocompleteapi';
import MockAutoCompleteService from './core/search/mockautocompleteservice';
Expand Down Expand Up @@ -163,6 +164,12 @@ class Answers {
resetListener: data => {
if (!data[StorageKeys.QUERY]) {
this.core.clearResults();
} else {
this.core.globalStorage.set(StorageKeys.QUERY_TRIGGER, QueryTriggers.QUERY_PARAMETER);
}

if (!data[StorageKeys.SEARCH_OFFSET]) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See note in the commit message. I made SLAP-616 to address going from presence to absence of query param on back nav.

this.core.globalStorage.set(StorageKeys.SEARCH_OFFSET, 0);
}
globalStorage.setAll(data);
}
Expand All @@ -173,6 +180,9 @@ class Answers {
globalStorage.set(StorageKeys.LOCALE, parsedConfig.locale);
globalStorage.set(StorageKeys.SESSIONS_OPT_IN, parsedConfig.sessionTrackingEnabled);
parsedConfig.noResults && globalStorage.set(StorageKeys.NO_RESULTS_CONFIG, parsedConfig.noResults);
if (globalStorage.getState(StorageKeys.QUERY)) {
globalStorage.set(StorageKeys.QUERY_TRIGGER, QueryTriggers.QUERY_PARAMETER);
}

const context = globalStorage.getState(StorageKeys.API_CONTEXT);
if (context && !isValidContext(context)) {
Expand Down Expand Up @@ -434,7 +444,7 @@ class Answers {
if (prepopulatedQuery != null) {
return;
}
this.core.globalStorage.set('queryTrigger', 'initialize');
this.core.globalStorage.set(StorageKeys.QUERY_TRIGGER, QueryTriggers.INITIALIZE);
this.core.setQuery(searchConfig.defaultInitialSearch);
}

Expand Down
28 changes: 23 additions & 5 deletions src/core/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Navigation from './models/navigation';
import AlternativeVerticals from './models/alternativeverticals';
import DirectAnswer from './models/directanswer';
import LocationBias from './models/locationbias';
import QueryTriggers from './models/querytriggers';

import StorageKeys from './storage/storagekeys';
import AnalyticsEvent from './analytics/analyticsevent';
Expand Down Expand Up @@ -172,7 +173,9 @@ export default class Core {
}

const locationRadiusFilterNode = this.getLocationRadiusFilterNode();

const queryTrigger = this.getQueryTriggerForSearchApi(
this.globalStorage.getState(StorageKeys.QUERY_TRIGGER)
);
return this._searcher
.verticalSearch(verticalKey, {
limit: this.globalStorage.getState(StorageKeys.SEARCH_CONFIG).limit,
Expand All @@ -184,7 +187,7 @@ export default class Core {
offset: this.globalStorage.getState(StorageKeys.SEARCH_OFFSET) || 0,
isDynamicFiltersEnabled: this._isDynamicFiltersEnabled,
skipSpellCheck: this.globalStorage.getState('skipSpellCheck'),
queryTrigger: this.globalStorage.getState('queryTrigger'),
queryTrigger: queryTrigger,
sessionTrackingEnabled: this.globalStorage.getState(StorageKeys.SESSIONS_OPT_IN),
sortBys: this.globalStorage.getState(StorageKeys.SORT_BYS),
locationRadius: locationRadiusFilterNode ? locationRadiusFilterNode.getFilter().value : null,
Expand Down Expand Up @@ -217,7 +220,7 @@ export default class Core {
this.globalStorage.set(StorageKeys.LOCATION_BIAS, data[StorageKeys.LOCATION_BIAS]);
}
this.globalStorage.delete('skipSpellCheck');
this.globalStorage.delete('queryTrigger');
this.globalStorage.delete(StorageKeys.QUERY_TRIGGER);

const exposedParams = {
verticalKey: verticalKey,
Expand Down Expand Up @@ -280,11 +283,14 @@ export default class Core {
this.globalStorage.set(StorageKeys.SPELL_CHECK, {});
this.globalStorage.set(StorageKeys.LOCATION_BIAS, {});

const queryTrigger = this.getQueryTriggerForSearchApi(
this.globalStorage.getState(StorageKeys.QUERY_TRIGGER)
);
return this._searcher
.universalSearch(queryString, {
geolocation: this.globalStorage.getState(StorageKeys.GEOLOCATION),
skipSpellCheck: this.globalStorage.getState('skipSpellCheck'),
queryTrigger: this.globalStorage.getState('queryTrigger'),
queryTrigger: queryTrigger,
sessionTrackingEnabled: this.globalStorage.getState(StorageKeys.SESSIONS_OPT_IN),
context: context,
referrerPageUrl: referrerPageUrl
Expand All @@ -299,7 +305,7 @@ export default class Core {
this.globalStorage.set(StorageKeys.SPELL_CHECK, data[StorageKeys.SPELL_CHECK]);
this.globalStorage.set(StorageKeys.LOCATION_BIAS, data[StorageKeys.LOCATION_BIAS]);
this.globalStorage.delete('skipSpellCheck');
this.globalStorage.delete('queryTrigger');
this.globalStorage.delete(StorageKeys.QUERY_TRIGGER);

const exposedParams = {
queryString: queryString,
Expand Down Expand Up @@ -497,6 +503,18 @@ export default class Core {
this.filterRegistry.clearLocationRadiusFilterNode();
}

/**
* Returns the query trigger for the search API given the SDK query trigger
* @param {QueryTriggers} queryTrigger SDK query trigger
* @returns {QueryTriggers} query trigger if accepted by the search API, null o/w
*/
getQueryTriggerForSearchApi (queryTrigger) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want to use queryTrigger both for persistent storage magic and also as something completely separate we send to the api, I think it would be easier to just have them be two separate things.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked offline. This is definitely not an ideal way to get context about entry for the query update. This is necessary because we have /two/ ways we need to fix (on a refresh and on a popstate from back nav). Both of these initiate a search through a query update.

Discussed w @tmeyer2115 a bit yesterday about the second point. QueryTriggers will be a first class SDK enum. We will guide behavior based on this storage key and if it has a certain value, we will also pass it in the LiveAPI request.

Keeping this implementation for now, I understand the refactoring worries:(

if (queryTrigger === QueryTriggers.QUERY_PARAMETER) {
return null;
}
return queryTrigger;
}

enableDynamicFilters () {
this._isDynamicFiltersEnabled = true;
}
Expand Down
12 changes: 12 additions & 0 deletions src/core/models/querytriggers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** @module QueryTriggers */

/**
* QueryTriggers is an ENUM of the possible triggers for a
* query update.
*
* @enum {string}
*/
export default {
INITIALIZE: 'initialize',
QUERY_PARAMETER: 'query-parameter'
};
3 changes: 2 additions & 1 deletion src/core/storage/storagekeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ export default {
LOCATION_RADIUS: 'location-radius',
RESULTS_HEADER: 'results-header',
API_CONTEXT: 'context',
REFERRER_PAGE_URL: 'referrerPageUrl'
REFERRER_PAGE_URL: 'referrerPageUrl',
QUERY_TRIGGER: 'queryTrigger'
};
7 changes: 1 addition & 6 deletions src/ui/components/filters/filteroptionscomponent.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
/** @module FilterOptionsComponent */

/* global Event */

import Component from '../component';
import { AnswersComponentError } from '../../../core/errors/errors';
import Filter from '../../../core/models/filter';
Expand Down Expand Up @@ -360,10 +358,7 @@ export default class FilterOptionsComponent extends Component {
if (clearSearchEl && searchInputEl) {
DOM.on(clearSearchEl, 'click', event => {
searchInputEl.value = '';
searchInputEl.dispatchEvent(new Event('input', {
'bubbles': true,
'cancelable': true
}));
DOM.trigger(searchInputEl, 'input');
searchInputEl.focus();
});
}
Expand Down
52 changes: 34 additions & 18 deletions src/ui/components/search/searchcomponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import Component from '../component';
import DOM from '../../dom/dom';
import StorageKeys from '../../../core/storage/storagekeys';
import QueryTriggers from '../../../core/models/querytriggers';
import SearchParams from '../../dom/searchparams';

const IconState = {
Expand Down Expand Up @@ -137,6 +138,15 @@ export default class SearchComponent extends Component {
*/
this._defaultInitialSearch = this._globalSearchConfig.defaultInitialSearch;

/**
* The default options for core search
* @type {Object}
*/
this._defaultSearchOptions = {
setQueryParams: true,
resetPagination: !!this._verticalKey
};

/**
* The query string to use for the input box, provided to template for rendering.
* Optionally provided
Expand All @@ -150,11 +160,22 @@ export default class SearchComponent extends Component {
}
if (q === null) {
if (this._defaultInitialSearch || this._defaultInitialSearch === '') {
this.core.globalStorage.set(StorageKeys.QUERY_TRIGGER, QueryTriggers.INITIALIZE);
this.core.setQuery(this._defaultInitialSearch);
}
return;
}
this.debouncedSearch(q);

const queryTrigger = this.core.globalStorage.getState(StorageKeys.QUERY_TRIGGER);
const resetPagination = this._verticalKey &&
queryTrigger !== QueryTriggers.QUERY_PARAMETER &&
queryTrigger !== QueryTriggers.INITIALIZE;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we anticipate making a QueryTrigger for manual input in the search bar? If not, could we simplify this logic by saying if verticalKey and if !queryTrigger, then resetPagination?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not sure what to anticipate! We currently have another query trigger (for SpellCheck component, which is more dynamic, relying on the type of the data returned). I believe we would want spellcheck query triggers to continue to reset pagination. It felt easier here to enumerate these two than try to figure out the possible types for SpellCheck. How does that sound to you?

const searchOptions = Object.assign(
{},
this._defaultSearchOptions,
{ resetPagination: resetPagination }
);
this.debouncedSearch(q, searchOptions);
});

/**
Expand Down Expand Up @@ -462,7 +483,7 @@ export default class SearchComponent extends Component {
this.core.persistentStorage.delete(StorageKeys.SEARCH_OFFSET);
this.core.globalStorage.delete(StorageKeys.SEARCH_OFFSET);
this.core.setQuery(query);
this.debouncedSearch(query);
this.debouncedSearch(query, this._defaultSearchOptions);
return false;
}

Expand Down Expand Up @@ -505,9 +526,10 @@ export default class SearchComponent extends Component {
* performed if we recently searched, if there's no query for universal search, or if this
* is a twin searchbar.
* @param {string} query The string to query against.
* @param {Object} searchOptions The options to pass for core search
* @returns {Promise} A promise that will perform the query and update globalStorage accordingly.
*/
debouncedSearch (query) {
debouncedSearch (query, searchOptions) {
if (this._throttled ||
(!query && !this._verticalKey) ||
(!query && this._verticalKey && !this._allowEmptySearch) ||
Expand Down Expand Up @@ -535,10 +557,10 @@ export default class SearchComponent extends Component {
lng: position.coords.longitude,
radius: position.coords.accuracy
});
resolve(this.search(query));
resolve(this.search(query, searchOptions));
},
() => {
resolve(this.search(query));
resolve(this.search(query, searchOptions));
const { enabled, message } = this._geolocationTimeoutAlert;
if (enabled) {
window.alert(message);
Expand All @@ -547,29 +569,23 @@ export default class SearchComponent extends Component {
this._geolocationOptions)
);
} else {
return this.search(query);
return this.search(query, searchOptions);
}
});
} else {
return this.search(query);
return this.search(query, searchOptions);
}
}

/**
* Performs a query using the provided string input.
* @param {string} query The string to query against.
* @param {Object} searchOptions The options to pass for core search
* @returns {Promise} A promise that will perform the query and update globalStorage accordingly.
*/
search (query) {
search (query, searchOptions) {
if (this._verticalKey) {
this.core.verticalSearch(
this._config.verticalKey,
{
resetPagination: true,
setQueryParams: true
},
{ input: query }
);
this.core.verticalSearch(this._config.verticalKey, searchOptions, { input: query });
} else {
// NOTE(billy) Temporary hack for DEMO
// Remove me after the demo
Expand All @@ -592,10 +608,10 @@ export default class SearchComponent extends Component {
urls[tabs[i].configId] = url;
}
}
return this.core.search(query, urls, { setQueryParams: true });
return this.core.search(query, urls, searchOptions);
}

return this.core.search(query, undefined, { setQueryParams: true });
return this.core.search(query, undefined, searchOptions);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/ui/components/search/spellcheckcomponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default class SpellCheckComponent extends Component {

onCreate () {
this.core.persistentStorage.delete('skipSpellCheck', true);
this.core.persistentStorage.delete('queryTrigger', true);
this.core.persistentStorage.delete(StorageKeys.QUERY_TRIGGER, true);
}

setState (data, val) {
Expand All @@ -49,7 +49,7 @@ export default class SpellCheckComponent extends Component {
let params = new SearchParams(window.location.search.substring(1));
params.set('query', query.value);
params.set('skipSpellCheck', true);
Comment on lines 50 to 51
Copy link
Member

@cea2aj cea2aj Sep 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Maybe we should use StorageKeys here as well? Everything else LGTM!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree! But I think that would be a change that should not be in this commit (as that is outside of the scope of this work). I can make an item for the more sweeping change of using StorageKeys wherever possible with global/persistent storage. SLAP-621

params.set('queryTrigger', type.toLowerCase());
params.set(StorageKeys.QUERY_TRIGGER, type.toLowerCase());
return '?' + params.toString();
}

Expand Down