Skip to content

Commit aa49bb8

Browse files
committed
feat(events): add separate timeline_focussed API route
1 parent 1324663 commit aa49bb8

File tree

6 files changed

+122
-57
lines changed

6 files changed

+122
-57
lines changed

invenio_requests/assets/semantic-ui/js/invenio_requests/api/InvenioRequestApi.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ export class RequestLinksExtractor {
2828
return this.#urls.timeline;
2929
}
3030

31+
get timelineFocussed() {
32+
if (!this.#urls.timeline) {
33+
throw TypeError("Timeline focussed link missing from resource.");
34+
}
35+
return this.#urls.timeline_focussed;
36+
}
37+
3138
get comments() {
3239
if (!this.#urls.comments) {
3340
throw TypeError("Comments link missing from resource.");
@@ -63,6 +70,16 @@ export class InvenioRequestsAPI {
6370
});
6471
};
6572

73+
getTimelineFocussed = async (focusEventId, params) => {
74+
return await http.get(this.#urls.timelineFocussed, {
75+
params: {
76+
expand: 1,
77+
focus_event_id: focusEventId,
78+
...params,
79+
},
80+
});
81+
};
82+
6683
getRequest = async () => {
6784
return await http.get(this.#urls.self, { params: { expand: 1 } });
6885
};

invenio_requests/assets/semantic-ui/js/invenio_requests/timeline/state/actions.js

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,29 @@ class intervalManager {
2626
}
2727
}
2828

29-
export const fetchTimeline = (includeEventId = undefined, loadingState = true) => {
29+
export const fetchTimeline = (focusEventId = undefined) => {
3030
return async (dispatch, getState, config) => {
3131
const state = getState();
3232
const { size, page, data: timelineData } = state.timeline;
3333

34-
if (loadingState) {
35-
dispatch({
36-
type: IS_LOADING,
37-
});
38-
}
3934
dispatch({
4035
type: IS_REFRESHING,
4136
});
4237

4338
try {
44-
const response = await config.requestsApi.getTimeline({
45-
size: size,
46-
page: page,
47-
sort: "oldest",
48-
include_event_id: includeEventId,
49-
});
39+
let response;
40+
if (focusEventId) {
41+
response = await config.requestsApi.getTimelineFocussed(focusEventId, {
42+
size: size,
43+
});
44+
} else {
45+
response = await config.requestsApi.getTimeline({
46+
size: size,
47+
page: page,
48+
sort: "oldest",
49+
include_event_id: focusEventId,
50+
});
51+
}
5052

5153
// Check if timeline has more events than the current state
5254
const hasMoreEvents = response.data?.hits?.total > timelineData?.hits?.total;
@@ -73,10 +75,7 @@ export const fetchTimeline = (includeEventId = undefined, loadingState = true) =
7375
});
7476
}
7577

76-
if (
77-
includeEventId &&
78-
!response.data.hits.hits.some((h) => h.id === includeEventId)
79-
) {
78+
if (focusEventId && !response.data.hits.hits.some((h) => h.id === focusEventId)) {
8079
// Show a warning if the event ID in the hash was not found in the response list of events.
8180
// This happens if the server cannot find the requested event.
8281
dispatch({
@@ -121,12 +120,15 @@ const timelineReload = (dispatch, getState, config) => {
121120

122121
if (concurrentRequests) return;
123122

124-
dispatch(fetchTimeline(undefined, false));
123+
dispatch(fetchTimeline());
125124
};
126125

127-
export const getTimelineWithRefresh = (includeEventId) => {
126+
export const getTimelineWithRefresh = (focusEventId) => {
128127
return async (dispatch, getState, config) => {
129-
dispatch(fetchTimeline(includeEventId, true));
128+
dispatch({
129+
type: IS_LOADING,
130+
});
131+
dispatch(fetchTimeline(focusEventId, true));
130132
dispatch(setTimelineInterval());
131133
};
132134
};

invenio_requests/resources/events/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
class RequestCommentsSearchRequestArgsSchema(SearchRequestArgsSchema):
2121
"""Add parameter to parse tags."""
2222

23-
include_event_id = fields.UUID()
23+
focus_event_id = fields.UUID()
2424

2525

2626
class RequestCommentsResourceConfig(RecordResourceConfig):
@@ -32,6 +32,7 @@ class RequestCommentsResourceConfig(RecordResourceConfig):
3232
"list": "/<request_id>/comments",
3333
"item": "/<request_id>/comments/<comment_id>",
3434
"timeline": "/<request_id>/timeline",
35+
"timeline_focussed": "/<request_id>/timeline_focussed",
3536
}
3637

3738
# Input

invenio_requests/resources/events/resource.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def create_url_rules(self):
6161
route("PUT", routes["item"], self.update),
6262
route("DELETE", routes["item"], self.delete),
6363
route("GET", routes["timeline"], self.search),
64+
route("GET", routes["timeline_focussed"], self.focussed_list),
6465
]
6566

6667
@list_view_args_parser
@@ -130,16 +131,28 @@ def search(self):
130131
"""Perform a search over EVENTS.
131132
132133
Its primary purpose is as a batch read of events i.e. the timeline.
133-
134-
By specifying `include_event_id`, you can ensure that the page containing the
135-
given event will be loaded. This will ignore the `page` argument.
136134
"""
137135
hits = self.service.search(
138136
identity=g.identity,
139137
request_id=resource_requestctx.view_args["request_id"],
140-
include_event_id=resource_requestctx.args.get("include_event_id"),
141138
params=resource_requestctx.args,
142139
search_preference=search_preference(),
143140
expand=resource_requestctx.args.get("expand", False),
144141
)
145142
return hits.to_dict(), 200
143+
144+
@list_view_args_parser
145+
@request_extra_args
146+
@search_args_parser
147+
@response_handler(many=True)
148+
def focussed_list(self):
149+
"""List the page containing the event with ID focus_event_id, or the first page of results if this is not found."""
150+
hits = self.service.focussed_list(
151+
identity=g.identity,
152+
request_id=resource_requestctx.view_args["request_id"],
153+
focus_event_id=resource_requestctx.args.get("focus_event_id"),
154+
page_size=resource_requestctx.args.get("size"),
155+
search_preference=search_preference(),
156+
expand=resource_requestctx.args.get("expand", False),
157+
)
158+
return hits.to_dict(), 200

invenio_requests/services/events/service.py

Lines changed: 64 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -238,13 +238,7 @@ def delete(self, identity, id_, revision_id=None, uow=None):
238238
return True
239239

240240
def search(
241-
self,
242-
identity,
243-
request_id,
244-
include_event_id=None,
245-
params=None,
246-
search_preference=None,
247-
**kwargs
241+
self, identity, request_id, params=None, search_preference=None, **kwargs
248242
):
249243
"""Search for events for a given request matching the querystring."""
250244
params = params or {}
@@ -255,45 +249,82 @@ def search(
255249
request = self._get_request(request_id)
256250
self.require_permission(identity, "read", request=request)
257251

258-
request_filter = dsl.Q("term", request_id=str(request.id))
259-
260252
# Prepare the search
261-
search = self._search(
253+
search_result = self._search(
262254
"search",
263255
identity,
264256
params,
265257
search_preference,
266258
permission_action="unused",
267-
extra_filter=request_filter,
259+
extra_filter=dsl.Q("term", request_id=str(request.id)),
268260
versioning=False,
269261
**kwargs,
262+
).execute()
263+
264+
return self.result_list(
265+
self,
266+
identity,
267+
search_result,
268+
params,
269+
links_tpl=LinksTemplate(
270+
self.config.links_search,
271+
context={"request_id": str(request_id), "args": params},
272+
),
273+
links_item_tpl=self.links_item_tpl,
274+
expandable_fields=self.expandable_fields,
275+
expand=expand,
270276
)
271277

272-
if include_event_id is not None:
273-
# If a specific event ID is requested, we need to work out the corresponding page number.
274-
event = None
275-
try:
276-
event = self._get_event(include_event_id)
277-
except sqlalchemy.exc.NoResultFound:
278-
# Silently ignore
279-
pass
280-
281-
if event is not None:
282-
self.require_permission(identity, "read", request=request, event=event)
283-
284-
num_older_than_event = search.filter(
285-
"range", created={"lt": event.created}
286-
).count()
287-
page = num_older_than_event // params["size"] + 1
288-
# Re run the pagination param interpreter to update the search with the new page number
289-
params.update(page=page)
290-
search = PaginationParam(self.config.search).apply(
291-
identity, search, params
292-
)
278+
def focussed_list(
279+
self,
280+
identity,
281+
request_id,
282+
focus_event_id,
283+
page_size,
284+
expand=False,
285+
search_preference=None,
286+
):
287+
"""Return a page of results focussed on a given event, or the first page if the event is not found."""
288+
289+
# Permissions - guarded by the request's can_read.
290+
request = self._get_request(request_id)
291+
self.require_permission(identity, "read", request=request)
292+
293+
# If a specific event ID is requested, we need to work out the corresponding page number.
294+
focus_event = None
295+
try:
296+
focus_event = self._get_event(focus_event_id)
297+
except sqlalchemy.exc.NoResultFound:
298+
# Silently ignore
299+
pass
300+
301+
params = {"sort": "oldest", "size": page_size}
302+
search = self._search(
303+
"search",
304+
identity,
305+
params,
306+
search_preference,
307+
permission_action="unused",
308+
extra_filter=dsl.Q("term", request_id=str(request.id)),
309+
versioning=False,
310+
)
311+
312+
page = 1
313+
if focus_event is not None:
314+
self.require_permission(
315+
identity, "read", request=request, event=focus_event
316+
)
317+
num_older_than_event = search.filter(
318+
"range", created={"lt": focus_event.created}
319+
).count()
320+
page = num_older_than_event // page_size + 1
321+
322+
# Re run the pagination param interpreter to update the search with the new page number
323+
params.update(page=page)
324+
search = PaginationParam(self.config.search).apply(identity, search, params)
293325

294326
# We deactivated versioning before (it doesn't apply for count queries) so we need to re-enable it.
295327
search_result = search.params(version=True).execute()
296-
297328
return self.result_list(
298329
self,
299330
identity,

invenio_requests/services/requests/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ class RequestsServiceConfig(RecordServiceConfig, ConfiguratorMixin):
107107
"self_html": RequestLink("{+ui}/requests/{id}"),
108108
"comments": RequestLink("{+api}/requests/{id}/comments"),
109109
"timeline": RequestLink("{+api}/requests/{id}/timeline"),
110+
"timeline_focussed": RequestLink("{+api}/requests/{id}/timeline_focussed"),
110111
}
111112
links_search = pagination_links("{+api}/requests{?args*}")
112113
links_user_requests_search = pagination_links("{+api}/user/requests{?args*}")

0 commit comments

Comments
 (0)