You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-26 17:03:12 +03:00
Enhancements to search results, and event context implementation
This change adds support to the JDK for processing the results of a room search, as well as back-paginating the results. It treats each search result as a 'context' object, which can itself be backwards or forward-paginated.
This commit is contained in:
committed by
review.rocks
parent
3e4cef89fd
commit
c669d21af7
184
lib/client.js
184
lib/client.js
@@ -12,6 +12,7 @@ var q = require("q");
|
||||
var httpApi = require("./http-api");
|
||||
var MatrixEvent = require("./models/event").MatrixEvent;
|
||||
var EventStatus = require("./models/event").EventStatus;
|
||||
var SearchResult = require("./models/search-result");
|
||||
var StubStore = require("./store/stub");
|
||||
var webRtcCall = require("./webrtc/call");
|
||||
var utils = require("./utils");
|
||||
@@ -1901,6 +1902,73 @@ MatrixClient.prototype.scrollback = function(room, limit, callback) {
|
||||
return defer.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Take an EventContext, and back/forward-fill results.
|
||||
*
|
||||
* @param {module:models/event-context.EventContext} eventContext context
|
||||
* object to be updated
|
||||
* @param {Object} opts
|
||||
* @param {boolean} opts.backwards true to fill backwards, false to go forwards
|
||||
* @param {boolean} opts.limit number of events to request
|
||||
*
|
||||
* @return {module:client.Promise} Resolves: updated EventContext object
|
||||
* @return {Error} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.paginateEventContext = function(eventContext, opts) {
|
||||
// TODO: we should implement a backoff (as per scrollback()) to deal more
|
||||
// nicely with HTTP errors.
|
||||
opts = opts || {};
|
||||
var backwards = opts.backwards || false;
|
||||
|
||||
var token = eventContext.getPaginateToken(backwards);
|
||||
if (!token) {
|
||||
// no more results.
|
||||
return q.reject(new Error("No paginate token"));
|
||||
}
|
||||
|
||||
var dir = backwards ? 'b' : 'f';
|
||||
var pendingRequest = eventContext._paginateRequests[dir];
|
||||
|
||||
if (pendingRequest) {
|
||||
// already a request in progress - return the existing promise
|
||||
return pendingRequest;
|
||||
}
|
||||
|
||||
var path = utils.encodeUri(
|
||||
"/rooms/$roomId/messages", {$roomId: eventContext.getEvent().getRoomId()}
|
||||
);
|
||||
var params = {
|
||||
from: token,
|
||||
limit: ('limit' in opts) ? opts.limit : 30,
|
||||
dir: dir
|
||||
};
|
||||
|
||||
var self = this;
|
||||
var promise =
|
||||
self._http.authedRequest(undefined, "GET", path, params
|
||||
).then(function(res) {
|
||||
var token = res.end;
|
||||
if (res.chunk.length === 0) {
|
||||
token = null;
|
||||
} else {
|
||||
var matrixEvents = utils.map(res.chunk, self.getEventMapper());
|
||||
if (backwards) {
|
||||
// eventContext expects the events in timeline order, but
|
||||
// back-pagination returns them in reverse order.
|
||||
matrixEvents.reverse();
|
||||
}
|
||||
eventContext.addEvents(matrixEvents, backwards);
|
||||
}
|
||||
eventContext.setPaginateToken(token, backwards);
|
||||
return eventContext;
|
||||
}).finally(function() {
|
||||
eventContext._paginateRequests[dir] = null;
|
||||
});
|
||||
eventContext._paginateRequests[dir] = promise;
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
// Registration/Login operations
|
||||
// =============================
|
||||
|
||||
@@ -2095,6 +2163,122 @@ MatrixClient.prototype.searchMessageText = function(opts, callback) {
|
||||
}, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Perform a server-side search for room events.
|
||||
*
|
||||
* The returned promise resolves to an object containing the fields:
|
||||
*
|
||||
* * {number} count: estimate of the number of results
|
||||
* * {string} next_batch: token for back-pagination; if undefined, there are
|
||||
* no more results
|
||||
* * {Array} highlights: a list of words to highlight from the stemming
|
||||
* algorithm
|
||||
* * {Array} results: a list of results
|
||||
*
|
||||
* Each entry in the results list is a {module:models/search-result.SearchResult}.
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @param {string} opts.term the term to search for
|
||||
* @param {Object} opts.filter a JSON filter object to pass in the request
|
||||
* @return {module:client.Promise} Resolves: result object
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.searchRoomEvents = function(opts) {
|
||||
// TODO: support groups
|
||||
|
||||
var body = {
|
||||
search_categories: {
|
||||
room_events: {
|
||||
search_term: opts.term,
|
||||
filter: opts.filter,
|
||||
order_by: "recent",
|
||||
event_context: {
|
||||
before_limit: 1,
|
||||
after_limit: 1,
|
||||
include_profile: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var searchResults = {
|
||||
_query: body,
|
||||
results: [],
|
||||
highlights: [],
|
||||
};
|
||||
|
||||
return this.search({body: body}).then(
|
||||
this._processRoomEventsSearch.bind(this, searchResults)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Take a result from an earlier searchRoomEvents call, and backfill results.
|
||||
*
|
||||
* @param {object} searchResults the results object to be updated
|
||||
* @return {module:client.Promise} Resolves: updated result object
|
||||
* @return {Error} Rejects: with an error response.
|
||||
*/
|
||||
MatrixClient.prototype.backPaginateRoomEventsSearch = function(searchResults) {
|
||||
// TODO: we should implement a backoff (as per scrollback()) to deal more
|
||||
// nicely with HTTP errors.
|
||||
|
||||
if (!searchResults.next_batch) {
|
||||
return q.reject(new Error("Cannot backpaginate event search any further"));
|
||||
}
|
||||
|
||||
if (searchResults.pendingRequest) {
|
||||
// already a request in progress - return the existing promise
|
||||
return searchResults.pendingRequest;
|
||||
}
|
||||
|
||||
var searchOpts = {
|
||||
body: searchResults._query,
|
||||
next_batch: searchResults.next_batch,
|
||||
};
|
||||
|
||||
var promise = this.search(searchOpts).then(
|
||||
this._processRoomEventsSearch.bind(this, searchResults)
|
||||
).finally(function() {
|
||||
searchResults.pendingRequest = null;
|
||||
});
|
||||
searchResults.pendingRequest = promise;
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* helper for searchRoomEvents and backPaginateRoomEventsSearch. Processes the
|
||||
* response from the API call and updates the searchResults
|
||||
*
|
||||
* @param {Object} searchResults
|
||||
* @param {Object} response
|
||||
* @return {Object} searchResults
|
||||
* @private
|
||||
*/
|
||||
MatrixClient.prototype._processRoomEventsSearch = function(searchResults, response) {
|
||||
var room_events = response.search_categories.room_events;
|
||||
|
||||
searchResults.count = room_events.count;
|
||||
searchResults.next_batch = room_events.next_batch;
|
||||
|
||||
// combine the highlight list with our existing list; build an object
|
||||
// to avoid O(N^2) fail
|
||||
var highlights = {};
|
||||
room_events.highlights.forEach(function(hl) { highlights[hl] = 1; });
|
||||
searchResults.highlights.forEach(function(hl) { highlights[hl] = 1; });
|
||||
|
||||
// turn it back into a list.
|
||||
searchResults.highlights = Object.keys(highlights);
|
||||
|
||||
// append the new results to our existing results
|
||||
for (var i = 0; i < room_events.results.length; i++) {
|
||||
var sr = SearchResult.fromJson(room_events.results[i], this.getEventMapper());
|
||||
searchResults.results.push(sr);
|
||||
}
|
||||
return searchResults;
|
||||
};
|
||||
|
||||
/**
|
||||
* Perform a server-side search.
|
||||
* @param {Object} opts
|
||||
|
||||
104
lib/models/event-context.js
Normal file
104
lib/models/event-context.js
Normal file
@@ -0,0 +1,104 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @module models/event-context
|
||||
*/
|
||||
|
||||
/**
|
||||
* Construct a new EventContext
|
||||
*
|
||||
* An eventcontext is used for circumstances such as search results, when we
|
||||
* have a particular event of interest, and a bunch of events before and after
|
||||
* it.
|
||||
*
|
||||
* It also stores pagination tokens for going backwards and forwards in the
|
||||
* timeline.
|
||||
*
|
||||
* @param {MatrixEvent} ourEvent the event at the centre of this context
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function EventContext(ourEvent) {
|
||||
this._timeline = [ourEvent];
|
||||
this._ourEventIndex = 0;
|
||||
this._paginateTokens = {b: null, f: null};
|
||||
|
||||
// this is used by MatrixClient to keep track of active requests
|
||||
this._paginateRequests = {b: null, f: null};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the main event of interest
|
||||
*
|
||||
* This is a convenience function for getTimeline()[getOurEventIndex()].
|
||||
*
|
||||
* @return {MatrixEvent} The event at the centre of this context.
|
||||
*/
|
||||
EventContext.prototype.getEvent = function() {
|
||||
return this._timeline[this._ourEventIndex];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of events in this context
|
||||
*
|
||||
* @return {Array} An array of MatrixEvents
|
||||
*/
|
||||
EventContext.prototype.getTimeline = function() {
|
||||
return this._timeline;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the index in the timeline of our event
|
||||
*
|
||||
* @return {Number}
|
||||
*/
|
||||
EventContext.prototype.getOurEventIndex = function() {
|
||||
return this._ourEventIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a pagination token.
|
||||
*
|
||||
* @param {boolean} backwards true to get the pagination token for going
|
||||
* backwards in time
|
||||
* @return {string}
|
||||
*/
|
||||
EventContext.prototype.getPaginateToken = function(backwards) {
|
||||
return this._paginateTokens[backwards ? 'b' : 'f'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Set a pagination token.
|
||||
*
|
||||
* Generally this will be used only by the matrix js sdk.
|
||||
*
|
||||
* @param {string} token pagination token
|
||||
* @param {boolean} backwards true to set the pagination token for going
|
||||
* backwards in time
|
||||
*/
|
||||
EventContext.prototype.setPaginateToken = function(token, backwards) {
|
||||
this._paginateTokens[backwards ? 'b' : 'f'] = token;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add more events to the timeline
|
||||
*
|
||||
* @param {Array} events new events, in timeline order
|
||||
* @param {boolean} atStart true to insert new events at the start
|
||||
*/
|
||||
EventContext.prototype.addEvents = function(events, atStart) {
|
||||
// TODO: should we share logic with Room.addEventsToTimeline?
|
||||
// Should Room even use EventContext?
|
||||
|
||||
if (atStart) {
|
||||
this._timeline = events.concat(this._timeline);
|
||||
this._ourEventIndex += events.length;
|
||||
} else {
|
||||
this._timeline = this._timeline.concat(events);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The EventContext class
|
||||
*/
|
||||
module.exports = EventContext;
|
||||
51
lib/models/search-result.js
Normal file
51
lib/models/search-result.js
Normal file
@@ -0,0 +1,51 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @module models/search-result
|
||||
*/
|
||||
|
||||
var EventContext = require("./event-context");
|
||||
var utils = require("../utils");
|
||||
|
||||
/**
|
||||
* Construct a new SearchResult
|
||||
*
|
||||
* @param {number} rank where this SearchResult ranks in the results
|
||||
* @param {event-context.EventContext} eventContext the matching event and its
|
||||
* context
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function SearchResult(rank, eventContext) {
|
||||
this.rank = rank;
|
||||
this.context = eventContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a SearchResponse from the response to /search
|
||||
* @static
|
||||
* @param {Object} jsonObj
|
||||
* @param {function} eventMapper
|
||||
* @return {SearchResult}
|
||||
*/
|
||||
|
||||
SearchResult.fromJson = function(jsonObj, eventMapper) {
|
||||
var jsonContext = jsonObj.context || {};
|
||||
var events_before = jsonContext.events_before || [];
|
||||
var events_after = jsonContext.events_after || [];
|
||||
|
||||
var context = new EventContext(eventMapper(jsonObj.result));
|
||||
|
||||
context.setPaginateToken(jsonContext.start, true);
|
||||
context.addEvents(utils.map(events_before, eventMapper), true);
|
||||
context.addEvents(utils.map(events_after, eventMapper), false);
|
||||
context.setPaginateToken(jsonContext.end, false);
|
||||
|
||||
return new SearchResult(jsonObj.rank, context);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The SearchResult class
|
||||
*/
|
||||
module.exports = SearchResult;
|
||||
Reference in New Issue
Block a user