[7257] Add proper pagination to Hosted and Remote Instances pages + N+1 fix for Hosted Instances#7353
[7257] Add proper pagination to Hosted and Remote Instances pages + N+1 fix for Hosted Instances#7353n-lark wants to merge 17 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7353 +/- ##
==========================================
+ Coverage 76.65% 76.71% +0.06%
==========================================
Files 410 410
Lines 20860 20920 +60
Branches 5061 5094 +33
==========================================
+ Hits 15990 16049 +59
- Misses 4870 4871 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…t to show total count on search, update specs
…s and filter tests for server-side pagination
…keep totals in sync
…ia allOf, matching the codebase convention
| includeSettings = false, | ||
| includeMeta = false, | ||
| limit = null, | ||
| offset = null, |
There was a problem hiding this comment.
Should we switch to a generic pagination object to house these params, similar to the device model? This method's arguments are getting out of hand really fast.
maybe set it up as an object with default values?
There was a problem hiding this comment.
Done. byTeam now takes a pagination object alongside the other opts. Defaults are inline so callers can pass a partial object or skip it entirely.
| orderByMostRecentFlows = false, | ||
| excludeApplications = null | ||
| excludeApplications = null, | ||
| withTotal = false |
There was a problem hiding this comment.
with total could be derived off if we're sending in the pagination object or not
There was a problem hiding this comment.
Yup, gone. withTotal now derives from pagination.page != null inside byTeam. Callers no longer pass it explicitly.
| properties: { | ||
| sort: { | ||
| type: 'string', | ||
| enum: ['name', 'createdAt', 'updatedAt', 'application.name', 'flowLastUpdatedAt'] |
There was a problem hiding this comment.
pagination params already include sort as an option, was this only so we can type the endpoint more tightly?
There was a problem hiding this comment.
Yeah PaginationParams.sort is just a string, so unknown values would silently fall through to the default order. The enum makes the route 400 instead.
| } | ||
|
|
||
| const projects = await app.db.models.Project.byTeam(request.params.teamId, options) | ||
| const queryResult = await app.db.models.Project.byTeam(request.params.teamId, options) |
There was a problem hiding this comment.
this is where we could send the pagination param in
| const options = { | ||
| includeSettings: true, | ||
| limit: request.query.limit, | ||
| limit, |
There was a problem hiding this comment.
we could extract the pagination options similarly to how we do it for devices?
const paginationOptions = app.db.controllers.Device.getDevicePaginationOptions(request)
There was a problem hiding this comment.
Done. New app.db.controllers.Project.getProjectPaginationOptions(request) mirrors the device pattern. ?limit= alone still gets the legacy array.
| Alerts.emit('Successfully deleted the device', 'confirmation') | ||
| this.deleteLocalCopyOfDevice(device) | ||
| this.$emit('delete-device') | ||
| if (typeof this.loadDevices === 'function') { |
There was a problem hiding this comment.
this has me thinking on why we need to do this if we're already deleting the local copy of the device (which should in turn remove it from the ui)
There was a problem hiding this comment.
deleteLocalCopyOfDevice only removes the row from the in-memory map. Refetching keeps totalRows (server-side count) honest and pulls in a backfill row from the next page so we don't show "29 of 30" with a missing row. Skipping the refetch leaves the footer drifting and rows missing on small pages.
| return { | ||
| loading: false, | ||
| // Gates the full-page spinner to first paint so refetches don't unmount the search input. | ||
| hasLoadedOnce: false, |
There was a problem hiding this comment.
this is hurting my brain. I'm not going to block it, but we need to understand why we need to resort to these sorts of things and address the underlying issue
There was a problem hiding this comment.
Yah issue was the table would remount so it felt glitchy flashy. So I went down the path of a skeleton loader first but ended up just gating the load on loading && !instancesMap.size so we don't flashy glitch on page change.
| pendingStateChange: 'pendingStateChange', | ||
| optimisticStateChange: 'optimisticStateChange', | ||
| status: 'meta.state' | ||
| status: 'status' |
There was a problem hiding this comment.
I may be wrong, but status and meta.status are not the same
There was a problem hiding this comment.
I think i'm getting what you're doing. you're now pre-loading every instance status by default, so we won't need to re-query all instances iteratively/in a cascade. I can't place exactly where you're doing that just yet
There was a problem hiding this comment.
nvm, found it, you're including includeMeta: true in the initial fetch data request
There was a problem hiding this comment.
the problem with that is that the byTeam method returns the db entries of the statuses (which do not include the transitory states instances can be in, only the stable ones - see https://flowfuse.com/docs/user/instance-states/#flowfuse-node-red-instance-states). the old getInstanceMeta was calling '/:instanceId/status' which in turn was getting the actual live state of the instance by calling await instance.liveState({ omitStorageFlows: true }) which retrieved it's data twofold, corroborating from the db and from the cache. the byTeam endpoint is not doing that
There was a problem hiding this comment.
the result is that the db instance status can be running (which it well may be), but its actual state could be (installing,pending etc)
There was a problem hiding this comment.
This little bit needs a rethink. My thinking is that the best way about it would be to make the byTeam method use the project's actual liveState. I'm sure there were reasons why this was not done this way in the first place, but now maybe that we have proper caching in place for statuses, we might get away with it.
@hardillb I'd appreciate your input on the above proposition
There was a problem hiding this comment.
For the liveState thing, isn't that what this does in Project.js? It runs through the same liveState method in the view layer the old endpointed used.
if (includeMeta) {
const liveState = await project.liveState()
result.meta = liveState?.meta
result.flowLastUpdatedAt = liveState?.flowLastUpdatedAt
}
So its like byTeam -> Project.js liveState in view -> we get back the correct pending, installing, starting state etc.
I also tested this for starting in the UI and it returns the pending state so it is working.
I may be wrong, but status and meta.status are not the same
They hold the same value on hosted-instance rows, we normalize meta.state onto r.status in api/team.js:223-224. The reason we map the column to status instead of meta.state is that the dashboard-role endpoint (/dashboard-instances) returns top-level status and no meta object, so the old meta.state mapping gave dashboard users undefined and a forever spinner. Reading status works for both response shapes.
| // Gates the full-page spinner to first paint so refetches don't unmount the search input. | ||
| hasLoadedOnce: false, | ||
| instancesMap: new Map(), | ||
| page: 1, |
There was a problem hiding this comment.
this is the 2nd time i've seen these params (granted without the default sort) but i'd say we could have a default object that we could instantiate where needed with these props and their defaults
| paginationProps () { | ||
| if (this.dashboardRoleOnly) return null | ||
| // Hide only when no page size could ever split the result — keeps the size selector reachable otherwise. | ||
| if (this.totalRows <= 10) return null |
There was a problem hiding this comment.
again, I feel this should be handled within the component and is a leak
|
Hey @cstns all feedback should be addressed - thanks! |
Description
See #7257 (comment)
Related Issue(s)
Resolves #7257
Checklist
flowforge.yml?FlowFuse/helmto update ConfigMap TemplateFlowFuse/CloudProjectto update values for Staging/ProductionLabels
area:migrationlabel