Skip to content

Conversation

@palkerecsenyi
Copy link
Member

@palkerecsenyi palkerecsenyi commented Oct 21, 2025

Closes #494


  • Added a "Copy link" button to events/comments in the UI, which copies the current URL with a hash containing the event ID.
  • When TimelineFeed is mounted, it sees if there's a relevant hash in the URL, and requests the server to return the page that contains that event (instead of just requesting the first page).
  • Added an argument to the request event search route to return the page containing a given event instead of a specific numbered page. The actual page number chosen is returned by the server (see feat(records): return page number in RecordList invenio-records-resources#656). This is calculated by counting the number of records older than the specified one and dividing by the page size.

Screenshots

image

This copies the following link:

https://127.0.0.1:5000/communities/pal/requests/8642288d-5938-4eb7-a435-728d7266aa09#commentevent-03a3b14c-1f52-46fd-a60a-5b00ceefd7a6

And shows up like this when navigated to:

image

@palkerecsenyi palkerecsenyi force-pushed the deep-links branch 2 times, most recently from 139ce3c to c1ee20b Compare October 21, 2025 07:39
)
search_result = search.execute()

if include_event_id is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add the include_event_id as new Param to isolate the behaviour of the filtering?

if (concurrentRequests) return;

dispatch(fetchTimeline(false));
dispatch(fetchTimeline(undefined, false));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to improve this?
fetchTimeline(undefined, false) looks... ugly?

Can we refactor that fetchTimeline and maybe remove the loadingState param (split in 2 different functions?) so that the new optional param is missing or defined?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the IS_LOADING dispatch into the getTimelineWithRefresh function which is the only place where loadingState = true was used, so this way I got rid of loadingState completely. Hope this is okay, the calls look less ugly now.

@zzacharo
Copy link
Member

@ntarocco regarding #503 (comment)

Just to be clear the new functionality returns the search results of the page that the given event_id falls into. So, in principle is a search but ensuring that the results you get back include the event you passed. I am happy to discuss for a new API, but how would it look like?

Currently, from REST API we have: /api/requests/<id>/timeline?include_event_id=<event_id>

@ntarocco
Copy link
Contributor

@ntarocco regarding #503 (comment)

Just to be clear the new functionality returns the search results of the page that the given event_id falls into. So, in principle is a search but ensuring that the results you get back include the event you passed. I am happy to discuss for a new API, but how would it look like?

Currently, from REST API we have: /api/requests/<id>/timeline?include_event_id=<event_id>

Yes understood, the feeling is that we are specializing the search endpoint with a specific behaviour. I would rather implement this specific logic in a dedicated method, which then calls the "generic search" passing the page or from/to or whatever with the classic API.

@palkerecsenyi
Copy link
Member Author

Ahh okay, I am happy to try this and we can see what it looks like! My main concern is making sure we keep to minimal code duplication, and this would probably do that.

Copy link
Contributor

@ntarocco ntarocco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks better to me :)
Don't forget to add the REST API endpoint in the doc, and mention it in the vNext release notes, thank you!

<Overridable id={`TimelineEvent.layout.${this.eventToType(event)}`} event={event}>
<RequestsFeed.Item>
<RequestsFeed.Item
id={`commentevent-${event.id}`}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you take this from the self_html link of the comment? Just to make it in sync in case someone changes it in the backend?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've resolved this as much as possible but I think we still have to hardcode the commentevent for the function that retrieves the event ID from the URL on page load.

return [EntityResolverExpandableField("created_by")]

def links_item_tpl_for_request(self, request):
def links_item_tpl_for_request_type(self, request_type: str):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm that s breaking change for the API of the class....Can we avoid it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I think the name only changed between two of my commits within this PR. The method is actually new, neither links_item_tpl_for_request nor links_item_tpl_for_request_type exists within the class currently.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you squash the commits maybe?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ntarocco @slint @kpsherva any objection to this?

)
# Override the _links_item_tpl as done by all other RecordList-returning methods in the
# RequestEvents service.
request_events._links_item_tpl = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we need to override this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In several methods of RequestEventsService, we are returning a self.result_list or self.result_item, which return RequestEventItem or RequestEventList respectively. By default (in some of the default method implementations of invenio_records_resources.services.records.RecordService), we return self.result_list passing self.links_item_tpl as the value for the links_item_tpl param, for example in the scan method.

However, in RequestEventsService, we cannot simply implement links_item_tpl as a property method, since we need to pass the request type into it so we can generate the correct variables for RequestEventLinks. So instead, everywhere we call result_list or result_item we pass self.links_item_tpl_for_request_type(str(request.type)) as the links_tpl or links_item_tpl value as appropriate.

However, we don't override the default scan method from RecordService in RequestEventsService. As a result, we are keeping its self.result_list call along with its default value of self.links_item_tpl. We could override the scan method entirely, but I went with the solution of just replacing the self._links_item_tpl of the returned RequestEventList class after it is returned.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fenekku any idea to make this more elegant maybe?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, after spending a lot of time thinking about this actually, I would say to implement scan (taking request_id like search) and defining the links_item_pl there. We really don't want to reach to _links_item_tpl since it's private.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for taking a look at this! I am happy to go for that approach, I will implement it now :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

# ResultItem configurations
links_item = {
"self": RequestEventLink("{+api}/requests/{request_id}/comments/{id}"),
"self_html": RequestEventLink("{+ui}/requests/{request_id}#commentevent-{id}"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: going to have to implement anchor to fully move away from Link

https://github.com/inveniosoftware/invenio-base/blob/master/invenio_base/urls/helpers.py#L22

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh I was also going to mention this but I forgot, I saw we are removing Link in v14. But at the moment I wasn't sure how to express this as an EndpointLink given the anchor. Thanks for catching it

Copy link
Contributor

@fenekku fenekku left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for that and kudos for going through that code. I was trying to work out "nicer" ways of dealing with all this all morning and nothing was particularly easier (more a commentary on the codebase we have 😄 ).

vars.update({"id": record.id, "request_id": record.request_id})
request_type = current_request_type_registry.lookup(vars["request_type"])
vars.update({"id": obj.id, "request_id": obj.request_id})
vars.update(request_type._update_link_config(**vars))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shelve: given that this pattern now has spread also to request events, it's time to actually revisit how we define request types in a way that keeps all the logic for the different layers (data, service, presentation, etc.) contained under a single interface.

We already have #332 which specifically describes the issue for links, but other bad examples are:

We should discuss in what way we can bring all of these implementation aspects under a single file/definition/interface.


# fetching all request events to get involved users
request_events = current_events_service.scan(
request_id=request["id"],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shelve: looking at this now, it's not great that we go through all of the comments to find the participants (imagine 10k comments between just 2 participants)... we should instead:

  • Keep this, but only fetch the created_by.user field via the source search parameter
  • Do an aggregation query (terms bucket) on created_by.user to fetch the unique user IDs

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracking this in #508

* Added a "Copy link" button to events/comments in the UI, which copies
the current URL with a hash containing the event ID.

* When TimelineFeed is mounted, it sees if there's a relevant hash in
the URL, and requests the server to return the page that contains that
event (instead of just requesting the first page).

* Added an argument to the request event `search` route to return the
page containing a given event instead of a specific numbered page. The
actual page number chosen is returned by the server (see
inveniosoftware/invenio-records-resources#656).
This is calculated by counting the number of records older than the
specified one and dividing by the page size.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable sharing of comments via a deep-link

5 participants