Skip to content

Migrate the facility plugin to the new resource methods - #15184

Merged
AlexVelezLl merged 8 commits into
learningequality:developfrom
rtibblesbot:issue-15062-fbe9fc
Aug 17, 2026
Merged

Migrate the facility plugin to the new resource methods#15184
AlexVelezLl merged 8 commits into
learningequality:developfrom
rtibblesbot:issue-15062-fbe9fc

Conversation

@rtibblesbot

@rtibblesbot rtibblesbot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Every resource call in facility moves to the new Resource methods; DeletedFacilityUserResource.restoreCollection is rewritten onto request.
  • The users table reads through useList. Deletes the dead plugin-local PortalResource.
  • Dropping the cache exposed a backend bug: member_of, related_to__in and user_type OR-ed a multi-valued relation, returning a user once per membership or role. Now a correlated Exists() subquery.
  • Two behaviour fixes from losing the Model cache's dirty check: a user-type-only save reads the user back rather than sending an empty PATCH, and the settings snapshot is now taken once both halves of a facility settings save are done, not after the login half.
  • updateClass takes a name rather than an arbitrary updateData, so its payload is already the diff with no cache to compute one.

References

Fixes #15062. Recipe: #15057.

Reviewer guidance

Code review

  • useFacilityEditor.js:220saveFacilityConfig passes settingsCopy as the update baseline, replacing the Model cache's diff. Check no path mutates settings without a matching copySettings().
  • userManagement/actions.js:62 — a role-only change now retrieves the user; check nothing downstream needed the PATCH response.
  • useUserManagement.js:73 — check the stale-page 404 redirect cannot loop, now error handling runs in a watch.
  • facility_user.py:71 — check no caller relied on a row per membership or role.
  • MoveToTrashModal.vue:90, AssignCoachesSidePanel.vue:178, EnrollLearnersSidePanel.vue:176, ClassCopyModal.vue:178 and RemoveFromClassSidePanel.vue:212 keep list(): their loading ref is also set by a write path, which useList cannot do.
  • Reads without force: true now hit the network every time.

Manual QA

No UI changes; the facility plugin should keep behaving the same. Both flows need a facility with a user other than yourself: on a fresh install finish the setup wizard, sign in as the super admin, then Facility > Users > New user to create a learner.

  1. Edit that user. Facility > Users → the user's ⋮ → Edit: change full name, username and identifier and save. Return and change only the user type — learner ↔ coach, then super admin on and off — touching no other field. Then ⋮ → Reset password, and ⋮ → Delete. Every change survives a reload; a user-type-only save applies the new type, and the deleted user is out of the table.
  2. Facility settings. Facility > Settings: tick one checkbox, Save changes, reload; then change several at once and save. Create PIN, reload, remove it. Switch the sign-in option between username-and-password, username-only and picture password. Each save persists across a reload — including the checkboxes saved alongside a sign-in change — and nothing you did not edit moves.

AI usage

Used Claude Code to apply the recipe call site by call site and diagnose the duplicate row. Verified with the facility and kolibri-common Jest suites, the auth Python suite, prek, and live QA.

Duplicate row, before and after:

Before After
Before After

Pages exercised live:

Page Screenshot
Class detail Class
Deleted users Trash
Data Data

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?
  • Ran pre-flight CI checks (lint, format, tests) and verified all pass
  • Rebased onto the target branch and resolved any conflicts
  • Reorganized commit history into clean, logical commits
  • Audited the diff to ensure only issue-relevant files are changed
  • Built PR body from the repository's PR template with evidence blocks

Committed separately: no commit on this branch introduced the lines these changes touch, so they are a new commit rather than folded into the work they amend:

  • kolibri/plugins/facility/frontend/modules/userManagement/__tests__/actions.spec.js
@rtibblesbot

🟡 Waiting for feedback

Last updated: 2026-08-17 16:33 UTC

@github-actions github-actions Bot added DEV: backend Python, databases, networking, filesystem... APP: Facility Re: Facility App (user/class management, facility settings, csv import/export, etc.) DEV: frontend SIZE: large labels Aug 12, 2026
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

@rtibblesbot
rtibblesbot force-pushed the issue-15062-fbe9fc branch 2 times, most recently from 39f2b74 to 7f68609 Compare August 12, 2026 17:11
@rtibblesbot
rtibblesbot marked this pull request as ready for review August 12, 2026 21:11

@AlexVelezLl AlexVelezLl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good job overall, @rtibblesbot! I think the major issue right now is the entire facility object being sent even if we just update a single setting. Beyond that, please add a "Manual QA" section on the PR description following these instructions (and that PR description example), there are several risky areas to be tested here, like the users filters handling, so please carefully describe them.

Comment thread kolibri/plugins/facility/frontend/modules/userManagement/actions.js Outdated
Comment thread kolibri/core/auth/viewsets/facility_user.py
id: facilityDatasetId.value,
data,
});
await FacilityDatasetResource.update(facilityDatasetId.value, data);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is no longer doing any diff checking; this is a regression from the old implementation. Please refactor this so that we can send a baseline to the update method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — saveFacilityConfig now passes settingsCopy as the update baseline, so only edited settings go on the wire (and an unchanged config sends nothing). settingsCopy is the snapshot last synced with the server: copySettings() runs on mount and after every save. Spec asserts the baseline.

id,
data: updateData,
}).then(
return ClassroomResource.update(id, updateData).then(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is only being used to edit the class name, right? Could you rather have updateData specify which specific fields it allows? So that we can better justify why we don't do any diff checking here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right, rename is the only use. The action now takes { id, name } and sends { name } — the one field it can change, so the payload is the diff by construction. ClassRenameModal updated to match.

@rtibblesbot
rtibblesbot force-pushed the issue-15062-fbe9fc branch 2 times, most recently from 9e061cb to 7ee2391 Compare August 14, 2026 16:19
Comment thread kolibri/plugins/facility/frontend/composables/useFacilityEditor.js Outdated
rtibblesbot and others added 8 commits August 14, 2026 13:07
The Model/Collection cache goes with them, so every read that did not pass
`force: true` now hits the network.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The resource is imported only by facility, so the recipe's in-place rewrite
applies rather than a `_v2` sibling. It now resolves with the payload instead
of the axios response, and failures are logged by `request`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four behaviour changes come with useFetch: `dataLoading` clears on failure
rather than spinning forever, `usersCount`/`totalPages` normalise to 0 instead
of undefined, a superseded fetch's response is discarded, and failure handling
runs from a watcher a tick later instead of a catch.

`fetchClasses` stays a plain `list()` — `useUsersFilters` dereferences the
`classes` ref before any fetch runs, and `useFetch` initialises `data` to null.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It duplicates packages/kolibri-common/apiResources/PortalResource.js and has
no importers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
member_of, related_to__in and user_type joined memberships and roles, returning
a user once per matching row - a learner in two classes appeared twice. The
legacy Collection cache deduplicated by id client-side, hiding it until the
resource migration.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Legacy saveModel dirty-checked and skipped the request when the payload was
empty; update() always PATCHes. A user-type change with no edited details sent
an empty PATCH purely to obtain roles for updateFacilityLevelRoles.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`saveConfig` saves login settings and then the rest of the config.
`saveFacilityLoginSettings` re-snapshotted `settingsCopy` as it finished, so
the config diff that follows saw no changes and `update` sent no request —
every edited setting was silently dropped behind a "saved" snackbar.

`saveFacilityConfig` already takes the snapshot once both halves are saved.
Dropping the early one also fixes the failure path, where `undoSettingsChange`
restored the edited state rather than the original.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@AlexVelezLl AlexVelezLl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Code changes look good, ready for QA!

@rtibblesbot

Copy link
Copy Markdown
Contributor Author

Holding off on changes while this PR is in 🧪 Needs QA — the branch will not move while it is being tested. Questions still get answered.

Change the board status when QA is done and I'll pick up any outstanding review feedback, CI failures and conflicts on the next cycle.


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?
  • Ran pre-flight CI checks (lint, format, tests) and verified all pass
  • Rebased onto the target branch and resolved any conflicts
  • Reorganized commit history into clean, logical commits
  • Audited the diff to ensure only issue-relevant files are changed
  • Built PR body from the repository's PR template with evidence blocks

@radinamatic

Copy link
Copy Markdown
Member

All the facility features listed in the QA guidance perform as expected: editing user details, deleting accounts, changing facility settings & PIN - everything persists across reloads, and no regressions or console errors are observed.

@rtibblesbot

Copy link
Copy Markdown
Contributor Author

Holding off on changes while this PR is in 🧪 Needs QA — the branch will not move while it is being tested. Questions still get answered.

Change the board status when QA is done and I'll pick up any outstanding review feedback, CI failures and conflicts on the next cycle.


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?
  • Ran pre-flight CI checks (lint, format, tests) and verified all pass
  • Rebased onto the target branch and resolved any conflicts
  • Reorganized commit history into clean, logical commits
  • Audited the diff to ensure only issue-relevant files are changed
  • Built PR body from the repository's PR template with evidence blocks

@rtibblesbot

Copy link
Copy Markdown
Contributor Author

Thanks @radinamatic. The branch is still ae36409 — nothing has been pushed since QA started, so what you tested is what is here.

Nothing outstanding on my side: all of @AlexVelezLl's review comments have replies and were confirmed. CI is green (the one pending check is the hourly contributor-check workflow, unrelated).

Merge is blocked only by the standing CHANGES_REQUESTED review — it needs a re-approval from @AlexVelezLl to clear.

Move this off 🧪 Needs QA and I'll pick up anything that lands afterwards.

@radinamatic radinamatic left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Delay in approval due to GH glitches...

LGTM! 💯 :shipit: 🚀

@AlexVelezLl AlexVelezLl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm! Thanks @radinamatic!

@AlexVelezLl
AlexVelezLl merged commit af3d48c into learningequality:develop Aug 17, 2026
130 checks passed
@rtibblesbot
rtibblesbot deleted the issue-15062-fbe9fc branch August 17, 2026 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

APP: Facility Re: Facility App (user/class management, facility settings, csv import/export, etc.) DEV: backend Python, databases, networking, filesystem... DEV: frontend SIZE: large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate facility to the new Resource-layer methods

3 participants