Repository navigation
🗃️ Make archiving tables/applications a personal option - #2476
AndyScherzinger wants to merge 8 commits into
Conversation
008f4f3 to
0e2bbdc
Compare
d3fce3e to
fd235a6
Compare
There was a problem hiding this comment.
Looks generally nice, 2 details
- I’d say to use outline icons instead of filled for both cases, archiving and unarchiving
- In the cases of "Archive app" and "Unarchive app", the sorting of actions in the menu is off. We always recommend the destructive action (Delete) to be the very last, and then the next most destructive like Archive could be directly above it.
fd235a6 to
ba16212
Compare
840a1ab to
086aad3
Compare
36faeb9 to
40aec24
Compare
40aec24 to
94ce7a1
Compare
340f674 to
794e3e8
Compare
2fb9108 to
42d3fa5
Compare
Add the tables_archive_user table and the contexts.archived column, the UserArchive entity and mapper, and the archived flag on the Table and Context entities. Non-owner archive decisions are stored as per-user override rows, owner decisions live on the entity flag. Assisted-by: ClaudeCode:claude-fable-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Owner archiving toggles the shared entity flag inside a transaction and clears every per-user override; non-owners store or drop a personal override. The service also resolves the per-user flag onto entity collections and migrates overrides when ownership is transferred. Assisted-by: ClaudeCode:claude-fable-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
…ices Enrich read paths with the per-user resolved archived flag, add archive/unarchive, keep the state consistent across ownership transfer and deletion, hide archived contexts from the navigation, and expose the archived field in the API response definitions. Assisted-by: ClaudeCode:claude-fable-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Add the v2 table and context archive/unarchive endpoints (rate-limited, permission-checked, contexts addressed via the string typeParam) and their routes, resolve the per-user state in the single-item GET endpoints, and leave the archived state unchanged on partial updates through API v1 and the occ command. Assisted-by: ClaudeCode:claude-fable-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Remove stale archive overrides when a share is deleted, a group or circle membership ends, a receiver is deleted, or a user account is removed. Per-user access re-checks are handed to a background job so an unrelated request does not scale with the number of override holders. Assisted-by: ClaudeCode:claude-fable-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Add archive/unarchive actions to the table and context navigation items, a collapsible archived-applications section, and the matching store actions that guard against a missing local item. Assisted-by: ClaudeCode:claude-fable-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Assisted-by: ClaudeCode:claude-fable-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Cover the archive service and cleanup service, the mapper, the archive/unarchive controller endpoints, the background job, and the archive/unarchive UI flows for tables and applications. Assisted-by: ClaudeCode:claude-fable-5 Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
42d3fa5 to
49bf195
Compare
enjeck
left a comment
There was a problem hiding this comment.
It seems like a lot of the docblock descriptions are wrong, which is confusing. Or am i misunderstanding them?
| while ($shareRow = $shareResult->fetchAssociative()) { | ||
| $receiverId = $shareRow['receiver']; | ||
| if ($receiverId !== $ownerId) { | ||
| $inserted += $this->upsertArchiveRecord($checkQb, $insertQb, $receiverId, $tableId); |
There was a problem hiding this comment.
This adds a personal archived=true row for each direct user share of an already archived table. So why do we need postSchemaChange()?
| * For every row in `tables_tables` where `archived = true`, insert one | ||
| * `tables_archive_user` record for the owner and one for each direct |
| $tableId = (int)$tableRow['id']; | ||
| $ownerId = $tableRow['ownership']; | ||
|
|
||
| // The owner deliberately gets no override row: the entity-level |
There was a problem hiding this comment.
This comment explains why the owner gets no personal row. The same reason applies to the direct share recipients below, whose rows would also keep the table archived after the owner unarchives, right. therefor, we could we skip the recipients too, which leaves postSchemaChange() with nothing to do?
| /** | ||
| * [api v2] Archive a table for the requesting user | ||
| * | ||
| * Owners archive the table for all users (clears per-user overrides). |
There was a problem hiding this comment.
This seems misleading, since the owner archive path does not clear personal rows. ArchiveService::archiveForUser() only sets tables_tables.archived. Could it say that the owner sets the default, and that users who made their own choice keep it?
| /** | ||
| * [api v2] Unarchive a table for the requesting user | ||
| * | ||
| * Owners unarchive the table for all users (clears per-user overrides). |
There was a problem hiding this comment.
similar as at https://github.com/nextcloud/tables/pull/2476/changes#r4309477879 . The owner archive path does not clear personal rows.
| /** | ||
| * [api v2] Archive a context for the requesting user | ||
| * | ||
| * Owners archive the context for all users (clears per-user overrides). |
There was a problem hiding this comment.
similar as at https://github.com/nextcloud/tables/pull/2476/changes#r4309477879 . The owner archive path does not clear personal rows.
| /** | ||
| * [api v2] Unarchive a context for the requesting user | ||
| * | ||
| * Owners unarchive the context for all users (clears per-user overrides). |
There was a problem hiding this comment.
similar as at https://github.com/nextcloud/tables/pull/2476/changes#r4309477879 . The owner archive path does not clear personal rows.
| * | ||
| * An owner toggles the entity-level flag and clears every per-user | ||
| * override, so all users inherit the owner's choice. Anyone else only |
There was a problem hiding this comment.
| * | |
| * An owner toggles the entity-level flag and clears every per-user | |
| * override, so all users inherit the owner's choice. Anyone else only | |
| * | |
| * An owner toggles the entity-level flag, | |
| * so all users inherit the owner's choice. Anyone else only |
| } catch (Exception $e) { | ||
| $this->logger->error($e->getMessage(), ['exception' => $e]); | ||
| } | ||
| return array_values(array_filter($contexts, static fn (Context $context) => !$context->isArchived())); |
There was a problem hiding this comment.
This hides archived applications from the Nextcloud app menu. The “Show in app list” checkbox still shows for archived applications, but toggling it has no visible effect. Should the menu hide or disable that checkbox while the application is archived?
| // flag leaves the archived state unchanged instead of unarchiving | ||
| $archived = $input->getOption('archived') ? true : null; |
There was a problem hiding this comment.
So there is now no way to unarchive a table with occ tables:rename ?
🖼️ Screenshots
The original ticket #2125 defined tables and views, while this PR tacked tables and apps. Given a clear view on how archived views should get displayed this could be added as a follow-up PR to not make this PR grow in review-size and complexity.
Summary
archivedflag on tables with a two-layer per-user model: an owner-level flag on the entity and a newtables_archive_usertable that stores individual user overrides, so archive state is resolved per-user at read time. When an owner archives a table or application, it is automatically treated as archived for every user with access, but any of those users may override this for themselves; an owner unarchiving resets the state for everyone by clearing all per-user overrides.tables_tables.archivedcolumn is repurposed from a simple global toggle into the owner-level signal, and the database migration converts all currently-archived tables into owner-archived entries to preserve existing data without disruption.ArchiveServicecentralizes all business logic for both resource types, four new API endpoints are split acrossApiTablesControllerandContextControllerfollowing existing naming conventions, and the frontend gains archive/unarchive actions in both navigation item menus alongside a new collapsible "Archived applications" section mirroring the already-existing "Archived tables" section.The database migration only covers the owner and direct user shares, not groups, etc. since they can't be resolved directly via SQL (think teams/groups). So in these cases archived tables would resurface until manually archived by the respective user. Also there is no cleanup for teams/groups, if a user is taken out of a team, the archive meta-info would not get deleted. Only simple but confusing way around it would be to prevent archiving/unarchiving for tables/apps where a user is neither the owner nor a user-share receiver. Expensive alternative would be a housekeeping job checking all these archive bits periodically, but that also sounds expensive with little use. Else we would need to consume a team/group changes event and check if some cleanup is needed. WDYT?
🏁 Checklist
/backport to stableX.X🤖 AI (if applicable)