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)
return q(false);
if (this.nextSearchBatch) {
if (this.state.searchResults.next_batch) {
debuglog("requesting more search results");
return this._getSearchBatch(this.state.searchTerm,
this.state.searchScope).then(true);
var searchPromise = MatrixClientPeg.get().backPaginateRoomEventsSearch(
this.state.searchResults);
return this._handleSearchResult(searchPromise);
} else {
debuglog("no more search results");
return q(false);
@ -486,10 +487,8 @@ module.exports = React.createClass({
this.setState({
searchTerm: term,
searchScope: scope,
searchResults: [],
searchResults: {},
searchHighlights: [],
searchCount: null,
searchCanPaginate: null,
});
// 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.nextSearchBatch = null;
this._getSearchBatch(term, scope).done();
// make sure that we don't end up showing results from
// 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
_getSearchBatch: function(term, scope) {
_handleSearchResult: function(searchPromise) {
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({
searchInProgress: true,
});
// make sure that we don't end up merging results from
// 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) {
return searchPromise.then(function(results) {
debuglog("search complete");
if (!self.state.searching || self.searchId != searchId) {
if (!self.state.searching || self.searchId != localSearchId) {
console.error("Discarding stale search results");
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
// strings which actually got matched for highlighting.
// 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 ];
var highlights = results.highlights;
if (highlights.indexOf(self.state.searchTerm) < 0) {
highlights = highlights.concat(self.state.searchTerm);
}
// append the new results to our existing results
var events = self.state.searchResults.concat(results.results);
// For overlapping highlights,
// favour longer (more specific) terms first
highlights = highlights.sort(function(a, b) { b.length - a.length });
self.setState({
searchHighlights: highlights,
searchResults: events,
searchCount: results.count,
searchCanPaginate: !!(results.next_batch),
searchResults: results,
});
self.nextSearchBatch = results.next_batch;
}, function(error) {
var ErrorDialog = sdk.getComponent("dialogs.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() {
var DateSeparator = sdk.getComponent('messages.DateSeparator');
var cli = MatrixClientPeg.get();
var ret = [];
var EventTile = sdk.getComponent('rooms.EventTile');
var cli = MatrixClientPeg.get();
// XXX: todo: merge overlapping results somehow?
// XXX: why doesn't searching on name work?
if (this.state.searchResults.results === undefined) {
// awaiting results
return [];
}
if (this.state.searchCanPaginate === false) {
if (this.state.searchResults.length == 0) {
var ret = [];
if (!this.state.searchResults.next_batch) {
if (this.state.searchResults.results.length == 0) {
ret.push(<li key="search-top-marker">
<h2 className="mx_RoomView_topMarker">No results</h2>
</li>
@ -625,9 +604,10 @@ module.exports = React.createClass({
var lastRoomId;
for (var i = this.state.searchResults.length - 1; i >= 0; i--) {
var result = this.state.searchResults[i];
var mxEv = new Matrix.MatrixEvent(result.result);
for (var i = this.state.searchResults.results.length - 1; i >= 0; i--) {
var result = this.state.searchResults.results[i];
var mxEv = result.context.getEvent();
if (!EventTile.haveTileForEvent(mxEv)) {
// XXX: can this ever happen? It will make the result count
@ -638,29 +618,28 @@ module.exports = React.createClass({
var eventId = mxEv.getId();
if (this.state.searchScope === 'All') {
var roomId = result.result.room_id;
var roomId = mxEv.getRoomId();
if(roomId != lastRoomId) {
ret.push(<li key={eventId + "-room"}><h1>Room: { cli.getRoom(roomId).name }</h1></li>);
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}
if (result.context.events_before[0]) {
var mxEv2 = new Matrix.MatrixEvent(result.context.events_before[0]);
if (EventTile.haveTileForEvent(mxEv2)) {
ret.push(<li key={eventId+"-1"} data-scroll-token={eventId+"-1"}><EventTile mxEvent={mxEv2} contextual={true} /></li>);
var timeline = result.context.getTimeline();
for (var j = 0; j < timeline.length; j++) {
var ev = timeline[j];
var highlights;
var contextual = (j != result.context.getOurEventIndex());
if (!contextual) {
highlights = this.state.searchHighlights;
}
}
ret.push(<li key={eventId+"+0"} data-scroll-token={eventId+"+0"}><EventTile mxEvent={mxEv} highlights={this.state.searchHighlights}/></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>);
if (EventTile.haveTileForEvent(ev)) {
ret.push(<li key={eventId+"+"+j} data-scroll-token={eventId+"+"+j}>
<EventTile mxEvent={ev} contextual={contextual} highlights={highlights} />
</li>);
}
}
}
@ -1292,7 +1271,7 @@ module.exports = React.createClass({
searchInfo = {
searchTerm : this.state.searchTerm,
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;
// don't display the search count until the search completes and
// gives us a non-null searchCount.
if (this.props.searchInfo && this.props.searchInfo.searchCount !== null) {
// gives us a valid (possibly zero) searchCount.
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>;
}