Repository navigation
Conversation
…rganization metadata
… user storage
Organizations now expose the size of each of their channels, plus the total,
through a new organization/{id}/channels endpoint. The organization page gets a
Channels tab listing name, description and size, with the total below the
table. A channel's size counts each file checksum once.
User storage no longer counts channels that belong to an organization, since
that storage is now attributed to the organization. Quotas still belong to
users; moving them to organizations is out of scope.
Also drops an unused select_related from Channel.get_resource_size.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
👋 Hi @yasinelmi, thanks for contributing! For the review process to begin, please verify that the following is satisfied:
📢✨ Before we assign a reviewer:
Also check that issue requirements are satisfied & you ran Pull requests that don't follow the guidelines will be closed. Reviewer assignment can take up to 2 weeks. |
|
📢✨ Before we assign a reviewer, we'll turn on |
🟡 Waiting for changesLast updated: 2026-10-06 20:12 UTC |
rtibblesbot
left a comment
There was a problem hiding this comment.
PR #6291: Settings > Storage shows a stale total after a channel joins or leaves an organization. The Channels tab and endpoint work as designed.
CI passing. Manual QA covered the Channels tab (en/ar, keyboard, axe AA: 0 violations). It also covered the new-tab link, the empty state and the private-org 404. Populated rows were XHR-mocked, because nothing sets Channel.organization. A scratch backend test confirmed the stale total.
- blocking: storage total not recalculated on organization change (
models.py). - suggestion: organization-channel files count against no quota.
- suggestion: a failed or 404 fetch shows "no channels yet".
- suggestion: org size rules differ from user storage and
get_resource_size. - suggestion: RTL direction, size wrapping and untruncated descriptions in the tab.
- suggestion: two frontend test assertions are too weak.
Written by rtibblesbot, an LLM-based coding agent.
| # Organization channels count towards the organization's storage, not the user's. | ||
| return ( | ||
| self.editable_channels.exclude(deleted=True) | ||
| .filter(organization__isnull=True) |
There was a problem hiding this comment.
blocking: Settings > Storage keeps showing the old total after a channel joins or leaves an organization.
- The page shows the cached
disk_space_used. Onlycalculate_user_storagerefreshes it. Channel.on_update(models.py:1477) recalculates editors' storage only whendeletedchanges.- Scratch test: after a channel joins an org,
disk_space_usedstays 275. The live value is 0. - The last scenario in
view-organization-storage.featurefails.
Please recalculate editors' storage when organization_id changes, as for deleted. Tracking that field must not mark main_tree changed. Then make the test assert disk_space_used instead of patching calculate_user_storage.
| return ( | ||
| self.editable_channels.exclude(deleted=True) | ||
| .filter(organization__isnull=True) | ||
| .values(tree_id=F("main_tree__tree_id")) |
There was a problem hiding this comment.
suggestion: Files in organization channels now count against no quota. This filter also feeds the upload quota checks. Each upload is still checked against the uploader's personal free space. #6163 scopes out quota changes. Is this intended? If so, please track it in the follow-up quota issue.
| and their total size. A channel's size counts each file checksum once. | ||
| """ | ||
| organization = self.get_object() | ||
| file_sizes = ( |
There was a problem hiding this comment.
suggestion: Org channel sizes follow different rules from user storage and get_resource_size.
- User storage excludes non-billable files (Perseus exports, null
file_format). This subquery doesn't. - So a channel moved into an org adds more to the org total than it removes from the user's.
get_resource_sizededupes on(checksum, file_size)and caches. This dedupes onchecksumand recomputes on every load.- A checksum shared by two org channels counts twice in the total. User storage counts it once.
- No test pins either behavior.
Consider one shared helper on Channel, so the later quota migration has a single rule.
| .distinct("checksum") | ||
| ) | ||
| channels = ( | ||
| Channel.filter_view_queryset(Channel.objects.all(), request.user) |
There was a problem hiding this comment.
suggestion: Non-members of a public org see a partial sum labelled "Total size". It covers only the public channels. Is that intended?
| channels.value = data.channels; | ||
| size.value = data.size; | ||
| }) | ||
| .finally(() => { |
There was a problem hiding this comment.
suggestion: A failed fetch shows "This organization has no channels yet." On rejection, channels stays empty and loading clears. Reproduced live: a non-member opening a private org's Channels tab gets a 404 and sees this message. Please expose an error ref and show a load-failure message instead.
| v-else | ||
| :class="{ notranslate: colIndex === 1 }" | ||
| > | ||
| {{ content }} |
There was a problem hiding this comment.
suggestion: Size values wrap mid-value. Long descriptions also render untruncated.
- At 1280px, sizes wrap as "560 / MB". In Arabic they wrap onto 3 lines. Please add
white-space: nowrapto the size cell. - A long description makes a very tall row. The Organization Storage (Read-Only Implementation) #6163 mockup truncates them. Please use
KTextTruncatoror a line clamp.
| renderTab(); | ||
|
|
||
| const link = await screen.findByRole('link', { name: /Alpha/ }); | ||
| expect(link).toHaveAttribute('target', '_blank'); |
There was a problem hiding this comment.
suggestion: This test passes with a wrong channel id or a missing href. It asserts only target. The jest window.Urls proxy ignores its argument (jest_config/setup.js:69). Please stub window.Urls.channel and assert the href.
| expect(within(rows[1]).getByText('10 MB')).toBeInTheDocument(); | ||
| expect(within(rows[2]).getByText('560 MB')).toBeInTheDocument(); | ||
| expect( | ||
| screen.getByText(organizationStrings.totalSize$({ size: '570 MB' })), |
There was a problem hiding this comment.
nitpick: This passes wherever the total renders on the page. Please scope the query to the total element.
|
|
||
| self.assertIn( | ||
| response.status_code, | ||
| (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN), |
There was a problem hiding this comment.
nitpick: This accepts a 401 the endpoint never returns. SessionAuthentication is listed first, so the response is always 403. Please pin HTTP_403_FORBIDDEN like the sibling tests (line 199).
| expected_usage = baseline_usage + non_perseus_size | ||
| self.assertEqual(self.user.get_space_used(), expected_usage) | ||
|
|
||
| def test_get_space_used_excludes_organization_channels(self): |
There was a problem hiding this comment.
praise: This goes through the public get_space_used() and fails if the organization__isnull filter is removed.
Summary
Read-only organization storage, as described in #6163:
GET /api/organization/{id}/channels/endpoint returning the organization's channels that the user can view (id,name,description,size) plus their totalsize. A channel's size counts each file checksum once, matchingChannel.get_resource_size. The total is the sum of the listed channel sizes, so it always matches the table.OrganizationChannelsTab(KDSKTable+KExternalLink, Composition API, no Vuex/Vuetify) showing Name (opens the channel in a new tab), Description and Size, with "Total size" below the table, following the Organization Details design.User.get_user_active_treesnow leaves out channels that belong to an organization, so a user's storage only counts channels they "own". This coversget_space_used,set_space_used,get_space_used_by_kindand the upload space checks.select_relatedfromChannel.get_resource_size(it was immediately followed by.values()).References
Closes #6163.
Builds on #6132, which is merged into this branch because the organization pages it adds are where the new tab lives. Until #6132 merges, this diff includes its changes. The commit specific to this PR is the last one (
Add organization storage sizes and exclude organization channels from user storage). I'll re-mergeunstableonce #6132 lands.Also related: #6161 (channel migration), which is how channels get attached to organizations in practice.
Reviewer guidance
Things worth a close look:
check_spaceand the staged-upload checks. Once a user's channel belongs to an organization, its files stop counting against the user's quota, and organizations don't have quotas yet (out of scope per the issue). So uploads into organization channels aren't limited by a quota until quotas move to organizations.Manual check:
Screenshots still to be added.
Testing
OrganizationChannelsTestCaseintests/viewsets/test_organization.py(sizes and total, checksum dedup, deleted and other-organization channels excluded, empty channel, private-organization 404, public-organization visibility, auth required) andtest_get_space_used_excludes_organization_channelsintests/test_files.py.pytestontests/viewsets,test_files.py,test_user.py,test_models.py,test_channel_model.py,test_storage_common.py: 714 passed, 5 skipped. The complete suite didn't finish within my local run's time limit, so CI is the full run.OrganizationChannelsTab.spec.jsplus a Channels-tab case inOrganizationEditPage.spec.js. AllchannelListandshared/datasuites pass (368 tests).integration_testing/features/manage-organizations/view-organization-storage.feature.pre-commitpasses on all changed files.AI usage
I used Claude Code (Claude Opus) to gather context from the issue, the design PDF and the overlapping PRs, and to draft the implementation, tests and this description. I decided how to interpret the user-storage requirement and the total-size semantics, reviewed the generated code against Studio's conventions, and ran the backend and frontend test suites and pre-commit locally.
🤖 Generated with Claude Code