Merge pull request #71 from matrix-org/rav/new_search_api

Use new searchRoomEvents and backPaginateRoomEventsSearch methods
pull/21833/head
Richard van der Hoff 2016-01-05 17:56:13 +00:00
commit 49c1d39f93
2 changed files with 78 additions and 99 deletions

View File

@ -364,10 +364,11 @@ module.exports = React.createClass({
if (!backwards) if (!backwards)
return q(false); return q(false);
if (this.nextSearchBatch) { if (this.state.searchResults.next_batch) {
debuglog("requesting more search results"); debuglog("requesting more search results");
return this._getSearchBatch(this.state.searchTerm, var searchPromise = MatrixClientPeg.get().backPaginateRoomEventsSearch(
this.state.searchScope).then(true); this.state.searchResults);
return this._handleSearchResult(searchPromise);
} else { } else {
debuglog("no more search results"); debuglog("no more search results");
return q(false); return q(false);
@ -486,10 +487,8 @@ module.exports = React.createClass({
this.setState({ this.setState({
searchTerm: term, searchTerm: term,
searchScope: scope, searchScope: scope,
searchResults: [], searchResults: {},
searchHighlights: [], searchHighlights: [],
searchCount: null,
searchCanPaginate: null,
}); });
// if we already have a search panel, we need to tell it to forget // if we already have a search panel, we need to tell it to forget
@ -498,64 +497,68 @@ module.exports = React.createClass({
this.refs.searchResultsPanel.resetScrollState(); this.refs.searchResultsPanel.resetScrollState();
} }
this.nextSearchBatch = null; // make sure that we don't end up showing results from
this._getSearchBatch(term, scope).done(); // an aborted search by keeping a unique id.
//
// todo: should cancel any previous search requests.
this.searchId = new Date().getTime();
var filter;
if (scope === "Room") {
filter = {
// XXX: it's unintuitive that the filter for searching doesn't have the same shape as the v2 filter API :(
rooms: [
this.props.roomId
]
};
}
debuglog("sending search request");
var searchPromise = MatrixClientPeg.get().searchRoomEvents({
filter: filter,
term: term,
});
this._handleSearchResult(searchPromise).done();
}, },
// fire off a request for a batch of search results _handleSearchResult: function(searchPromise) {
_getSearchBatch: function(term, scope) { var self = this;
// keep a record of the current search id, so that if the search terms
// change before we get a response, we can ignore the results.
var localSearchId = this.searchId;
this.setState({ this.setState({
searchInProgress: true, searchInProgress: true,
}); });
// make sure that we don't end up merging results from return searchPromise.then(function(results) {
// different searches by keeping a unique id.
//
// todo: should cancel any previous search requests.
var searchId = this.searchId = new Date().getTime();
var self = this;
debuglog("sending search request");
return MatrixClientPeg.get().search({ body: this._getSearchCondition(term, scope),
next_batch: this.nextSearchBatch })
.then(function(data) {
debuglog("search complete"); debuglog("search complete");
if (!self.state.searching || self.searchId != searchId) { if (!self.state.searching || self.searchId != localSearchId) {
console.error("Discarding stale search results"); console.error("Discarding stale search results");
return; return;
} }
var results = data.search_categories.room_events; // postgres on synapse returns us precise details of the strings
// which actually got matched for highlighting.
//
// In either case, we want to highlight the literal search term
// whether it was used by the search engine or not.
// postgres on synapse returns us precise details of the var highlights = results.highlights;
// strings which actually got matched for highlighting. if (highlights.indexOf(self.state.searchTerm) < 0) {
highlights = highlights.concat(self.state.searchTerm);
// combine the highlight list with our existing list; build an object
// to avoid O(N^2) fail
var highlights = {};
results.highlights.forEach(function(hl) { highlights[hl] = 1; });
self.state.searchHighlights.forEach(function(hl) { highlights[hl] = 1; });
// turn it back into an ordered list. For overlapping highlights,
// favour longer (more specific) terms first
highlights = Object.keys(highlights).sort(function(a, b) { b.length - a.length });
// sqlite doesn't give us any highlights, so just try to highlight the literal search term
if (highlights.length == 0) {
highlights = [ term ];
} }
// append the new results to our existing results // For overlapping highlights,
var events = self.state.searchResults.concat(results.results); // favour longer (more specific) terms first
highlights = highlights.sort(function(a, b) { b.length - a.length });
self.setState({ self.setState({
searchHighlights: highlights, searchHighlights: highlights,
searchResults: events, searchResults: results,
searchCount: results.count,
searchCanPaginate: !!(results.next_batch),
}); });
self.nextSearchBatch = results.next_batch;
}, function(error) { }, function(error) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
@ -569,48 +572,24 @@ module.exports = React.createClass({
}); });
}, },
_getSearchCondition: function(term, scope) {
var filter;
if (scope === "Room") {
filter = {
// XXX: it's unintuitive that the filter for searching doesn't have the same shape as the v2 filter API :(
rooms: [
this.props.roomId
]
};
}
return {
search_categories: {
room_events: {
search_term: term,
filter: filter,
order_by: "recent",
event_context: {
before_limit: 1,
after_limit: 1,
include_profile: true,
}
}
}
}
},
getSearchResultTiles: function() { getSearchResultTiles: function() {
var DateSeparator = sdk.getComponent('messages.DateSeparator'); var DateSeparator = sdk.getComponent('messages.DateSeparator');
var cli = MatrixClientPeg.get();
var ret = [];
var EventTile = sdk.getComponent('rooms.EventTile'); var EventTile = sdk.getComponent('rooms.EventTile');
var cli = MatrixClientPeg.get();
// XXX: todo: merge overlapping results somehow? // XXX: todo: merge overlapping results somehow?
// XXX: why doesn't searching on name work? // XXX: why doesn't searching on name work?
if (this.state.searchResults.results === undefined) {
// awaiting results
return [];
}
if (this.state.searchCanPaginate === false) { var ret = [];
if (this.state.searchResults.length == 0) {
if (!this.state.searchResults.next_batch) {
if (this.state.searchResults.results.length == 0) {
ret.push(<li key="search-top-marker"> ret.push(<li key="search-top-marker">
<h2 className="mx_RoomView_topMarker">No results</h2> <h2 className="mx_RoomView_topMarker">No results</h2>
</li> </li>
@ -625,9 +604,10 @@ module.exports = React.createClass({
var lastRoomId; var lastRoomId;
for (var i = this.state.searchResults.length - 1; i >= 0; i--) { for (var i = this.state.searchResults.results.length - 1; i >= 0; i--) {
var result = this.state.searchResults[i]; var result = this.state.searchResults.results[i];
var mxEv = new Matrix.MatrixEvent(result.result);
var mxEv = result.context.getEvent();
if (!EventTile.haveTileForEvent(mxEv)) { if (!EventTile.haveTileForEvent(mxEv)) {
// XXX: can this ever happen? It will make the result count // XXX: can this ever happen? It will make the result count
@ -638,29 +618,28 @@ module.exports = React.createClass({
var eventId = mxEv.getId(); var eventId = mxEv.getId();
if (this.state.searchScope === 'All') { if (this.state.searchScope === 'All') {
var roomId = result.result.room_id; var roomId = mxEv.getRoomId();
if(roomId != lastRoomId) { if(roomId != lastRoomId) {
ret.push(<li key={eventId + "-room"}><h1>Room: { cli.getRoom(roomId).name }</h1></li>); ret.push(<li key={eventId + "-room"}><h1>Room: { cli.getRoom(roomId).name }</h1></li>);
lastRoomId = roomId; lastRoomId = roomId;
} }
} }
var ts1 = result.result.origin_server_ts; var ts1 = mxEv.getTs();
ret.push(<li key={ts1 + "-search"}><DateSeparator ts={ts1}/></li>); // Rank: {resultList[i].rank} ret.push(<li key={ts1 + "-search"}><DateSeparator ts={ts1}/></li>); // Rank: {resultList[i].rank}
if (result.context.events_before[0]) { var timeline = result.context.getTimeline();
var mxEv2 = new Matrix.MatrixEvent(result.context.events_before[0]); for (var j = 0; j < timeline.length; j++) {
if (EventTile.haveTileForEvent(mxEv2)) { var ev = timeline[j];
ret.push(<li key={eventId+"-1"} data-scroll-token={eventId+"-1"}><EventTile mxEvent={mxEv2} contextual={true} /></li>); var highlights;
var contextual = (j != result.context.getOurEventIndex());
if (!contextual) {
highlights = this.state.searchHighlights;
} }
} if (EventTile.haveTileForEvent(ev)) {
ret.push(<li key={eventId+"+"+j} data-scroll-token={eventId+"+"+j}>
ret.push(<li key={eventId+"+0"} data-scroll-token={eventId+"+0"}><EventTile mxEvent={mxEv} highlights={this.state.searchHighlights}/></li>); <EventTile mxEvent={ev} contextual={contextual} highlights={highlights} />
</li>);
if (result.context.events_after[0]) {
var mxEv2 = new Matrix.MatrixEvent(result.context.events_after[0]);
if (EventTile.haveTileForEvent(mxEv2)) {
ret.push(<li key={eventId+"+1"} data-scroll-token={eventId+"+1"}><EventTile mxEvent={mxEv2} contextual={true} /></li>);
} }
} }
} }
@ -1292,7 +1271,7 @@ module.exports = React.createClass({
searchInfo = { searchInfo = {
searchTerm : this.state.searchTerm, searchTerm : this.state.searchTerm,
searchScope : this.state.searchScope, searchScope : this.state.searchScope,
searchCount : this.state.searchCount, searchCount : this.state.searchResults.count,
}; };
} }

View File

@ -109,8 +109,8 @@ module.exports = React.createClass({
var searchStatus; var searchStatus;
// don't display the search count until the search completes and // don't display the search count until the search completes and
// gives us a non-null searchCount. // gives us a valid (possibly zero) searchCount.
if (this.props.searchInfo && this.props.searchInfo.searchCount !== null) { if (this.props.searchInfo && this.props.searchInfo.searchCount !== undefined && this.props.searchInfo.searchCount !== null) {
searchStatus = <div className="mx_RoomHeader_searchStatus">&nbsp;(~{ this.props.searchInfo.searchCount } results)</div>; searchStatus = <div className="mx_RoomHeader_searchStatus">&nbsp;(~{ this.props.searchInfo.searchCount } results)</div>;
} }