Skip to content

Commit

Permalink
Merge pull request #7 from palantirnet/root-138-autocomplete
Browse files Browse the repository at this point in the history
Root-198: add search-as-you-type suggestion query functionality
  • Loading branch information
jesconstantine authored Mar 13, 2019
2 parents 2df2db3 + f0df907 commit 18f3092
Show file tree
Hide file tree
Showing 9 changed files with 2,733 additions and 2,015 deletions.
4,229 changes: 2,241 additions & 1,988 deletions build/index.js

Large diffs are not rendered by default.

22 changes: 21 additions & 1 deletion src/api/server.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import xhr from "xhr";
import solrQuery from "./solr-query";
import solrQuery, { solrSuggestQuery } from "./solr-query";

const MAX_INT = 2147483647;

Expand Down Expand Up @@ -33,6 +33,26 @@ server.submitQuery = (query, callback) => {
});
};

server.submitSuggestQuery = (suggestQuery, callback) => {
callback({type: "SET_SUGGESTIONS_PENDING"});

server.performXhr({
url: suggestQuery.url,
data: solrSuggestQuery(suggestQuery),
method: "POST",
headers: {
"Content-type": "application/x-www-form-urlencoded",
...(suggestQuery.userpass ? {"Authorization": "Basic " + suggestQuery.userpass} : {}),
}
}, (err, resp) => {
if (resp.statusCode >= 200 && resp.statusCode < 300) {
callback({type: "SET_SUGGESTIONS", data: JSON.parse(resp.body)});
} else {
console.log("Server error: ", resp.statusCode);
}
});
};

server.fetchCsv = (query, callback) => {
server.performXhr({
url: query.url,
Expand Down
41 changes: 41 additions & 0 deletions src/api/solr-client.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import queryReducer from "../reducers/query";
import resultReducer from "../reducers/results";
import suggestionReducer from "../reducers/suggestions";
import suggestQueryReducer from "../reducers/suggestQuery";
// import { submitQuery, fetchCsv } from "./server";
import server from "./server";

Expand Down Expand Up @@ -84,6 +86,39 @@ class SolrClient {
});
}

setSuggestQuery(query, autocomplete, value) {
const {searchFields} = query;
// Add the current text field value to the searchFields array.
const newFields = searchFields
.map((searchField) => searchField.field === query.mainQueryField ? {...searchField, value: value} : searchField);
const payload = {
type: "SET_SUGGEST_QUERY",
suggestQuery: {
searchFields: newFields,
sortFields: query.sortFields,
filters: query.filters,
userpass: query.userpass,
mainQueryField: query.mainQueryField,
start: 0,
mode: autocomplete.mode,
url: autocomplete.url,
rows: autocomplete.suggestionRows || 5,
appendWildcard: autocomplete.appendWildcard || false,
value
}
};
this.sendSuggestQuery(suggestQueryReducer(this.state.suggestQuery, payload));
}

sendSuggestQuery(suggestQuery = this.state.suggestQuery) {
this.state.suggestQuery = suggestQuery;
server.submitSuggestQuery(suggestQuery, (action) => {
this.state.suggestions = suggestionReducer(this.state.suggestions, action);
this.state.suggestQuery = suggestQueryReducer(this.state.suggestQuery, action);
this.onChange(this.state, this.getHandlers());
});
}

sendNextCursorQuery() {
server.submitQuery(this.state.query, (action) => {
this.state.results = resultReducer(this.state.results, {
Expand Down Expand Up @@ -132,6 +167,11 @@ class SolrClient {
const payload = {type: "SET_SEARCH_FIELDS", newFields: newFields};

this.sendQuery(queryReducer(this.state.query, payload));
// Enable the the autosuggest input to be cleared cleared
// but only if autcomplete has been configured.
if (Object.hasOwnProperty.call(this.state, "suggestQuery")) {
this.state.suggestQuery = suggestQueryReducer(this.state.suggestQuery, payload);
}
}

setFacetSort(field, value) {
Expand Down Expand Up @@ -172,6 +212,7 @@ class SolrClient {

getHandlers() {
return {
onTextInputChange: this.setSuggestQuery.bind(this),
onSortFieldChange: this.setSortFieldValue.bind(this),
onSearchFieldChange: this.setSearchFieldValue.bind(this),
onFacetSortChange: this.setFacetSort.bind(this),
Expand Down
85 changes: 65 additions & 20 deletions src/api/solr-query.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,30 +84,18 @@ const buildFormat = (format) => Object.keys(format)
.join("&");

const buildMainQuery = (fields, mainQueryField) => {
let qs = "q=";
let params = fields.filter(function (searchField) {
return searchField.field === mainQueryField;
}).map(function (searchField) {
return fieldToQueryFilter(searchField);
return searchField.value;
});
// If there are multiple main query fields, join them.
if (params.length > 1) {
qs += params.join("&");
}
// If there is only one main query field, add only it.
else if (params.length === 1) {
if (params[0] !== null) {
qs += params[0];
} else {
// If query field exists but is null send the wildcard query.
qs += "*:*";
}
}
// If there are no main query fields, send the wildcard query.
else {
qs += "*:*";
// Add value of the mainQueryField to the q param, if there is one.
if (params[0]) {
return `q=${params[0]}`;
}
return qs;

// If query field exists but is null/empty/undefined send the wildcard query.
return "q=*:*";
};

const buildHighlight = (highlight) => {
Expand Down Expand Up @@ -185,6 +173,61 @@ const solrQuery = (query, format = {wt: "json"}) => {

export default solrQuery;

const buildSuggestQuery = (fields, mainQueryField, appendWildcard) => {
let qs = "q=";
let params = fields.filter(function (searchField) {
return searchField.field === mainQueryField;
}).map(function (searchField) {
// Remove spaces on either end of the value.
const trimmed = searchField.value.trim();
// One method of supporting search-as-you-type is to append a wildcard '*'
// to match zero or more additional characters at the end of the users search term.
// @see: https://lucene.apache.org/solr/guide/6_6/the-standard-query-parser.html#TheStandardQueryParser-WildcardSearches
// @see: https://opensourceconnections.com/blog/2013/06/07/search-as-you-type-with-solr/
if (appendWildcard && trimmed.length > 0) {
// Split into word chunks.
const words = trimmed.split(" ");
// If there are multiple chunks, join them with "+", repeat the last word + append "*".
if (words.length > 1) {
return `${words.join("+")}+${words.pop()}*`;
}
// If there is only 1 word, repeat it an append "*".
return `${words}+${words}*`;
}
// If we are not supposed to append a wildcard, just return the value.
// ngram tokens/filters should be set up in solr config for
// the autocomplete endpoint request handler.
return trimmed;
});

if (params[0]) {
qs += params[0];
}

return qs;
};

const solrSuggestQuery = (suggestQuery, format = {wt: "json"}) => {
const {
rows,
searchFields,
filters,
appendWildcard,
} = suggestQuery;

const mainQueryField = Object.hasOwnProperty.call(suggestQuery, "mainQueryField") ? suggestQuery.mainQueryField : null;

const queryFilters = (filters || []).map((filter) => ({...filter, type: filter.type || "text"}));
const mainQuery = buildSuggestQuery(searchFields.concat(queryFilters), mainQueryField, appendWildcard);
const queryParams = buildQuery(searchFields.concat(queryFilters), mainQueryField);
const facetFieldParam = facetFields(searchFields);

return mainQuery +
`${queryParams.length > 0 ? `&${queryParams}` : ""}` +
`${facetFieldParam.length > 0 ? `&${facetFieldParam}` : ""}` +
`&rows=${rows}` +
`&${buildFormat(format)}`;
};

export {
rangeFacetToQueryFilter,
Expand All @@ -194,9 +237,11 @@ export {
fieldToQueryFilter,
buildQuery,
buildMainQuery,
buildSuggestQuery,
buildHighlight,
facetFields,
facetSorts,
buildSort,
solrQuery
solrQuery,
solrSuggestQuery
};
35 changes: 35 additions & 0 deletions src/reducers/suggestQuery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const initialState = {};

const setSuggestQuery = (state, action) => {
return {
...action.suggestQuery
};
};

const setSuggestQueryField = (state, action) => {
// Clear the suggestQueryField data only if the search field has been cleared.
if (action.newFields.filter(field => field.field === "tm_rendered_item" && field.value ==="").length) {
return Object.assign({},
...state,
{
suggestQuery: {
value: ""
}
},
);
}
return {
...state
};
};

export default function (state = initialState, action) {
switch (action.type) {
case "SET_SUGGEST_QUERY":
return setSuggestQuery(state, action);
case "SET_SEARCH_FIELDS":
return setSuggestQueryField(state, action);
}

return state;
}
22 changes: 22 additions & 0 deletions src/reducers/suggestions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const initialState = {
suggestionsPending: false,
docs: []
};

export default function (state = initialState, action) {
switch (action.type) {
case "SET_SUGGESTIONS":
return {
...state,
docs: action.data.response ? action.data.response.docs : [],
suggestionsPending: false
};

case "SET_SUGGESTIONS_PENDING":
return {
...state, suggestionsPending: true
};
}

return state;
}
Loading

0 comments on commit 18f3092

Please sign in to comment.