Saved Searches Pagination#44347
Conversation
Bundle size report 📦
Look at the Statoscope report for a full comparison between the commits 18f3243 and a39ae21 or learn more. Open explanation
|
b704543 to
3b5a621
Compare
3b5a621 to
aaaf67a
Compare
vdavid
left a comment
There was a problem hiding this comment.
I went through the code a few times, and didn't see any problems, it all seems decent to me!
BuildLimitOffsetArgs is a nice addition to the helpers; I find it weird that it didn't exist before.
There was a problem hiding this comment.
I've just recently been getting started with Go, so not sure if it's standard practice, but I've seen many contructor functions being called NewSomething. In this case, wouldn't NewLimitOffset be a better name for this function?
Edit: on a second thought, it may return nil, so perhaps the name NewLimitOffset could be misleading.
There was a problem hiding this comment.
All of our existing pagination is based on PageInfo type defined in graphql (IIRC). Which in turn is based on cursor pagination, not offset and limit.
I think we can reuse it instead of introducing our own thing. The convention is then to ask for
first: Int
after: String
Where first is basically the limit as you use it. And after is the cursor that points to the item after which want to get the next page worth of items. You can see it in action on org members here.
There was a problem hiding this comment.
@kopancek Agreed! I've already discussed this with @thenamankumar as well, here's some additional context: https://sourcegraph.slack.com/archives/C01C3NCGD40/p1668508044348929?thread_ts=1668499559.791709&cid=C01C3NCGD40
Ideally we can implement a proper cursor based navigation to avoid doing a LIMIT ... OFFSET ... query and avoid sequential scans on the database level :)
There was a problem hiding this comment.
I mean, it probably is OK for saved searches to do limit offset from a performance point of view, as there's not going to be many rows. But having a generic solution for proper cursors would be best, agreed :)
So I'm not totally oposed to using limit/offset pagination on the DB level here for now, but would like to keep the existing interface on the graphql layer, as I do not see the benefit of changing it for this specific feature.
There was a problem hiding this comment.
Do we already have an existing paginated list component that you can just reuse here?
There was a problem hiding this comment.
I think we can hide the page selector if totalCount <= PAGE_SIZE, but feel free to verify this with some designer, e.g. Rob or Quinn Keast.
There was a problem hiding this comment.
Supplying NamespaceType should not be necessary as graphql.ID already encodes the namespace in it. E.g. it is just Org:42 or User:12 type of thing.
You can use UnmarshalNamespaceID or NamespaceByID to get what you need here.
So all in all, this should be sufficient:
| func (r *schemaResolver) SavedSearchesByNamespace(ctx context.Context, args *struct { | |
| NamespaceType string | |
| NamespaceId graphql.ID | |
| }) (*savedSearchesConnectionResolver, error) { | |
| func (r *schemaResolver) SavedSearchesByID(ctx context.Context, args *struct { ID graphql.ID }) (*savedSearchesConnectionResolver, error) { |
There was a problem hiding this comment.
Just trying to understand, would it be too expensive to ensure this on the SQL level as well?
There was a problem hiding this comment.
What happens if orgID is nil in this case?
aaaf67a to
0f62a5f
Compare
* WIP add usePageinatedConnection * WIP add test and make first page navigatnion work * Add remaining functions * More test cases, backward pagination working
| func (p *ConnectionPageInfo[N]) HasNextPage() bool { | ||
| if p.args.Before != nil { | ||
| return true | ||
| } | ||
|
|
||
| return p.fetchedNodesCount > p.args.Limit() | ||
| } | ||
|
|
||
| func (p *ConnectionPageInfo[N]) HasPreviousPage() bool { | ||
| if p.args.After != nil { | ||
| return true | ||
| } | ||
|
|
||
| if p.args.Before != nil { | ||
| return p.fetchedNodesCount > p.args.Limit() | ||
| } | ||
|
|
||
| return false | ||
| } |
There was a problem hiding this comment.
I've been playing around with this locally and noticed a bug in this implementation in backward pagination mode. Try out this one:
| func (p *ConnectionPageInfo[N]) HasNextPage() bool { | |
| if p.args.Before != nil { | |
| return true | |
| } | |
| return p.fetchedNodesCount > p.args.Limit() | |
| } | |
| func (p *ConnectionPageInfo[N]) HasPreviousPage() bool { | |
| if p.args.After != nil { | |
| return true | |
| } | |
| if p.args.Before != nil { | |
| return p.fetchedNodesCount > p.args.Limit() | |
| } | |
| return false | |
| } | |
| func (p *ConnectionPageInfo[N]) HasNextPage() bool { | |
| if p.args.First != nil { | |
| return p.fetchedNodesCount > p.args.Limit() | |
| } | |
| return p.args.Before != nil | |
| } | |
| func (p *ConnectionPageInfo[N]) HasPreviousPage() bool { | |
| if p.args.Last != nil { | |
| return p.fetchedNodesCount > p.args.Limit() | |
| } | |
| return p.args.After != nil | |
| } |
| } | ||
|
|
||
| type ConnectionNode interface { | ||
| ID() graphql.ID |
There was a problem hiding this comment.
| ID() graphql.ID |
The ID() is actually not required as we call to marshalCursor instead. I recommend we remove it because this way we can also make it work for legacy routes that can not be paginated in sql (e.g. git endpoints). E.g. I’m migrating the repo contributors which is not backend by a SQL query so the individual returns have no ID
closes: https://github.com/sourcegraph/sourcegraph/issues/43623
closes: https://github.com/sourcegraph/sourcegraph/issues/43418
There is no pagination present (neither BE nor FE) for saved searches page under user settings and org settings. All the saved searches for under the user's namespace along with all saved searches under all the orgs the user is part of are fetched at once and then filtered based on the page on the frontend.
That sucks, so this PR introduces offset-based pagination at the backend, along with new resolvers to get saved searches by namespace. The new API is then used to show PageSelector component for pagination on the frontend.
TODO
Screenshots
Org Settings Saved Searches
User Settings Saved Searches
admin view for non logged in user's saved searches
admin view for non member organisation's saved searches
non-admin access to an inaccessible page.
Test plan
https://www.loom.com/share/ebeec2dea8e94b52a680012f5e4761fd