Migrate the device plugin to the new resource-layer methods - #15162
Conversation
|
The issue's acceptance criteria, with where each was verified. Recorded here because this account cannot edit the issue body (
Behavioural deltas this accepts
|
npm Package VersionsWarning The following packages have changed files but no version bump:
If these changes affect published code, consider bumping the version. |
37d1901 to
47513c5
Compare
Build Artifacts
Smoke test screenshot |
50e4f40 to
0542543
Compare
0542543 to
dd091c5
Compare
AlexVelezLl
left a comment
There was a problem hiding this comment.
Thanks, this is looking good, just a few minor changes. After this, could you please add a "Manual QA" section on the PR body following the guidelines on this comment? Just update the description.
| // `CoreTable` unmounts its rows while `dataLoading`, so only the first fetch may show it - | ||
| // a refetch triggered by a completed sync must not blank the rows already on screen. | ||
| const loadingFacilities = computed(() => loading.value && facilitiesData.value === null); |
There was a problem hiding this comment.
I don't get this; why is this different from just using loading? Is this fixing a bug in the previous implementation?
There was a problem hiding this comment.
Not a bug fix — it preserves the behaviour that was there. loadingFacilities was a data flag initialised true and set false by the beforeMount fetch, then never set back: the refetches (the watched-task handler after a sync completes, and handleClickClearAll) left it false.
useList sets loading for every fetch, and CoreTable renders dataLoading ? null : tbody (packages/kolibri/components/CoreTable.vue:112), so passing loading straight through would unmount the rows on each background refetch — a facility table that blanks itself every time a sync finishes. loading && data === null keeps the loader to the first load.
There was a problem hiding this comment.
This page definitely lacks good loading handling, but we won't address that here. thanks.
There was a problem hiding this comment.
No change here, then — loadingFacilities keeps the pre-existing first-load-only behaviour.
There was a problem hiding this comment.
Could we introduce useList here smoothly?
There was a problem hiding this comment.
Not smoothly. useList owns data, and this page does not own its collection: it filters on the way in (:147) and removes the selected rows locally on delete, restoring them if the task POST fails (:154,168-170).
Converting means the raw response in data, a computed for the filter, and a third ref of optimistically-removed ids to subtract from it — three pieces where there is now one array, and no reactive read gained, since the fetch is a one-shot in beforeMount. RearrangeChannelsPage has the same shape (optimistic reorder plus rollback) and got the same plain list() rename. The two that did convert, FacilitiesPage and useLodDeviceUsers, have collections written only by the fetch itself.
There was a problem hiding this comment.
Yeah, and also because channelIsBeingDeleted is coming from a Vuex module, let's keep it as it.
There was a problem hiding this comment.
Kept as is — plain list(), with the Vuex channelIsBeingDeleted filter still applied on the way in.
| async function postJobAction(resource, action, jobId) { | ||
| const response = await resource.request({ | ||
| method: 'POST', | ||
| action, | ||
| routeParams: jobId, | ||
| data: {}, | ||
| }); | ||
| return response.data; | ||
| } | ||
|
|
There was a problem hiding this comment.
Let's skip these utility methods that only obfuscate the actual implementation; the request method is simple enough to have more wrapper methods on top of it.
There was a problem hiding this comment.
Dropped — cancel_v2, clear_v2 and restart_v2 each spell out their own this.request({ ... }) call.
dd091c5 to
65d9261
Compare
65d9261 to
eb9b73b
Compare
There was a problem hiding this comment.
Let's remove it, I dont think it's adding any value
| method: 'POST', | ||
| action: 'cancel', | ||
| routeParams: jobId, | ||
| data: {}, |
There was a problem hiding this comment.
Do we need to specify this empty data? Would it work the same if we didn't?
There was a problem hiding this comment.
No — dropped from all three. cancel/clear/restart never read request.data (kolibri/core/tasks/viewsets/tasks.py:288,308,324); the empty body was only mirroring what accessDetailEndpoint happened to send. clearAll_v2 now passes undefined when there is no queue.
eb9b73b to
a44ef3b
Compare
a44ef3b to
c60cb64
Compare
AlexVelezLl
left a comment
There was a problem hiding this comment.
Code changes look good, should be good for QA review :)
|
|
@radinamatic thanks — answers to the three questions: 1 & 3 — 2 — a failed task needs the work itself to error, easiest by cutting the source mid-import:
The reorder problem is #15175, already present on |
The device plugin migrates to the new resource methods, but TaskResource is shared with plugins that have not. The _v2 methods keep the originals' signatures and return response.data; the originals stay until every consumer has moved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fetchModel/fetchCollection become retrieve/list, and the force flag goes with the cache it controlled. Two comments explaining cache behaviour are deleted: RemoteChannelResource.retrieve returns a single object either way, and a token lookup can no longer return a stale model. RearrangeChannelsPage and DeleteExportChannelsPage get a plain list() rename rather than useList - both mutate the fetched collection after the fetch, so useList cannot own the data. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Studio status call also stops destructuring - getKolibriStudioStatus_v2 resolves with the body rather than the whole axios response. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
saveModel chose POST or PATCH from cache state: a user with no DevicePermissions row had a 404 recorded against the model and got a POST. DevicePermissionsViewSet is a plain ModelViewSet over a OneToOne primary key, so an unconditional update would 404 on a first grant. The store already carries the discriminator - showUserPermissionsPage substitutes a row without a `user` key on 404 - so the fork reads that rather than catching a 404 and retrying. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both hand-rolled the loading flag and the fetch state that useList owns. FacilitiesPage writes its collection in exactly one place, and useLodDeviceUsers now derives `kind` in a computed rather than writing it onto the fetched objects. A failed LOD user fetch used to leave the page spinning forever - handleApiError re-throws by default, so the catch never reached its loading reset. useFetch resolves on failure instead, and the error watcher passes shouldThrow: false. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
c60cb64 to
fe2988a
Compare
|
Tested the remaining scenarios of resuming and restarting failed import tasks, no regressions found, we are good to go! |
Summary
retrieve/list, the one write becomescreate/update, and the facilities and LOD user lists becomeuseList.TaskResourceis shared with unmigrated plugins, so its four custom methods gain_v2siblings rather than in-place rewrites.References
Closes #15060. Recipe: #15057; methods from #15056.
getKolibriStudioStatus_v2landed with #15155, so this branch only points the device call site at it. Acceptance criteria, ticked here because this account cannot write the issue body (403):devicecall site on an old base or cache method — the Task 6 greps return nothing._v2siblings; originals kept for their other callers.useListconversions; nouseRetrievecandidate exists.pnpm test-jest --testPathPatterns "plugins/device": 22 suites, 163 tests green.Reviewer guidance
userPermissions/actions.js:19—createvsupdateforks on whether aDevicePermissionsrow exists, since PATCH 404s on a first grant. Both branches tested.TaskResource.clearAll_v2— queue goes in the body; the siblinglistaction reads the query string, so aparams:wrapper would clear every queue.FacilitiesPage/index.vue:262—loading && data === null, becauseCoreTableunmounts rows whiledataLoadingand a post-sync refetch must not blank the table.forcenow always hit the network, and a 404 used as control flow (mistyped token,wizard/utils.js:16) logs a console group. That one belongs inrequest.QA steps
Checked by @radinamatic; all five pass.
Screenshots
useListuseListcreate/updateforkFirst grant sent
POST …/devicepermissions/→ 201; a change sentPATCH …/<id>/→ 200.AI usage
Claude Code applied the #15057 recipe and wrote the new
addOrUpdateUserPermissionsspec. Verified with Jest, prek, and manual QA.@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Deviations from the issue spec
DeleteExportChannelsPage.vuekeeps an imperativelist()instead ofuseList— @AlexVelezLl asked to leave it, since itschannelIsBeingDeletedfilter comes from a Vuex module.🟡 Waiting for feedback
Last updated: 2026-08-11 16:50 UTC