1
0
mirror of https://github.com/quay/quay.git synced 2026-01-26 06:21:37 +03:00

fix(ui): display quota consumed for organizations and user namespaces (PROJQUAY-9641) (#4422)

superusers can now see quota consumed data in the organizations list for
both organizations and user namespaces. the fix preserves quota_report data
from the /api/v1/superuser/organizations/ endpoint instead of discarding it.

Signed-off-by: Brady Pratt <bpratt@redhat.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
jbpratt
2025-10-30 14:18:02 -05:00
committed by GitHub
parent 57a5e3368f
commit 1f434698a4
4 changed files with 135 additions and 7 deletions

View File

@@ -135,4 +135,111 @@ describe('Org List Page', () => {
cy.get('td[data-label="Name"]').should('have.length', 20);
cy.contains('1 - 20 of 30').should('exist');
});
it('Superuser displays quota consumed column (PROJQUAY-9641)', () => {
// This test verifies that superusers can see quota consumed data
// for organizations and user namespaces in the organizations list
// Mock config with quota features enabled
cy.fixture('config.json').then((config) => {
config.features.QUOTA_MANAGEMENT = true;
config.features.EDIT_QUOTA = true;
config.features.SUPER_USERS = true;
config.features.SUPERUSERS_FULL_ACCESS = true;
cy.intercept('GET', '/config', config).as('getConfig');
});
// Mock superuser
cy.fixture('superuser.json').then((user) => {
cy.intercept('GET', '/api/v1/user/', user).as('getSuperUser');
});
// Mock superuser organizations with quota_report data
cy.fixture('superuser-organizations.json').then((orgsData) => {
// Add quota_report to organizations
orgsData.organizations[0].quota_report = {
quota_bytes: 10737418240,
configured_quota: 53687091200,
};
orgsData.organizations[1].quota_report = {
quota_bytes: 5368709120,
configured_quota: 21474836480,
};
cy.intercept('GET', '/api/v1/superuser/organizations/', orgsData).as(
'getSuperuserOrganizations',
);
});
// Mock superuser users with quota_report data
cy.fixture('superuser-users.json').then((usersData) => {
// Add quota_report to users
usersData.users[0].quota_report = {
quota_bytes: 2147483648,
configured_quota: 10737418240,
};
cy.intercept('GET', '/api/v1/superuser/users/', usersData).as(
'getSuperuserUsers',
);
});
// Mock organization details
cy.intercept('GET', '/api/v1/organization/testorg', {
statusCode: 200,
body: {
name: 'testorg',
email: 'testorg@example.com',
teams: {owners: 'admin'},
},
});
cy.intercept('GET', '/api/v1/organization/projectquay', {
statusCode: 200,
body: {
name: 'projectquay',
email: 'projectquay@example.com',
teams: {},
},
});
cy.intercept('GET', '/api/v1/organization/coreos', {
statusCode: 200,
body: {
name: 'coreos',
email: 'coreos@example.com',
teams: {owners: 'admin'},
},
});
// Mock robots/members/repositories for all organizations
cy.intercept('GET', '/api/v1/organization/*/robots', {
statusCode: 200,
body: {robots: []},
});
cy.intercept('GET', '/api/v1/organization/*/members', {
statusCode: 200,
body: {members: []},
});
cy.intercept('GET', '/api/v1/repository?namespace=*', {
statusCode: 200,
body: {repositories: []},
});
cy.visit('/organization');
cy.wait('@getConfig');
cy.wait('@getSuperUser');
cy.wait('@getSuperuserOrganizations');
cy.wait('@getSuperuserUsers');
// Verify the Size column header exists for superusers
cy.contains('th', 'Size').should('exist');
// Verify quota data cells are visible and contain actual data
cy.get('td[data-label="Size"]').should('exist');
// Verify at least one organization shows quota consumed data (not "—")
// The quota should be displayed as "10.0 GiB / 50.0 GiB" format
cy.get('td[data-label="Size"]').first().should('not.contain.text', '—');
});
});

View File

@@ -6,6 +6,7 @@ import {
searchOrgsState,
} from 'src/atoms/OrganizationListState';
import {SearchState} from 'src/components/toolbar/SearchTypes';
import {IQuotaReport} from 'src/libs/quotaUtils';
import {
bulkDeleteOrganizations,
createOrg,
@@ -19,6 +20,7 @@ export type OrganizationDetail = {
isUser: boolean;
userEnabled?: boolean;
userSuperuser?: boolean;
quota_report?: IQuotaReport;
};
export function useOrganizations() {
@@ -68,19 +70,25 @@ export function useOrganizations() {
const organizationsTableDetails = [] as OrganizationDetail[];
for (const orgname of orgnames) {
// Find the organization object to get quota_report
const orgObj = (superUserOrganizations || []).find(
(o) => o.name === orgname,
);
organizationsTableDetails.push({
name: orgname,
isUser: false,
quota_report: orgObj?.quota_report,
});
}
for (const username of usernames) {
// Find the user's enabled status from superUserUsers
// Find the user's enabled status and quota_report from superUserUsers
const userObj = (superUserUsers || []).find((u) => u.username === username);
organizationsTableDetails.push({
name: username,
isUser: true,
userEnabled: userObj?.enabled,
userSuperuser: userObj?.super_user,
quota_report: userObj?.quota_report,
});
}

View File

@@ -480,6 +480,7 @@ export default function OrganizationsList() {
userEnabled={org.userEnabled}
userSuperuser={org.userSuperuser}
userEmail={org.isUser ? userEmailMap[org.name] : undefined}
quota_report={org.quota_report}
></OrgTableData>
</Tr>
))}

View File

@@ -43,6 +43,7 @@ function RepoLastModifiedDate(props: RepoLastModifiedDateProps) {
interface OrgTableDataProps extends OrganizationsTableItem {
userEmail?: string;
quota_report?: import('src/libs/quotaUtils').IQuotaReport;
}
// Get and assemble data from multiple endpoints to show in Org table
@@ -182,13 +183,24 @@ export default function OrgTableData(props: OrgTableDataProps) {
config?.features?.EDIT_QUOTA && (
<Td dataLabel={ColumnNames.size}>
{props.isUser ? (
<span style={{color: '#888'}}></span>
props.quota_report ? (
renderQuotaConsumed(props.quota_report, {
showPercentage: true,
showTotal: true,
showBackfill: true,
})
) : (
<span style={{color: '#888'}}></span>
)
) : (
renderQuotaConsumed(organization?.quota_report, {
showPercentage: true,
showTotal: true,
showBackfill: true,
})
renderQuotaConsumed(
props.quota_report || organization?.quota_report,
{
showPercentage: true,
showTotal: true,
showBackfill: true,
},
)
)}
</Td>
)}