Skip to content

Commit 6e7c8c5

Browse files
new: [STORIF-187] - Global quota usage table created.
1 parent 0f20350 commit 6e7c8c5

File tree

12 files changed

+307
-22
lines changed

12 files changed

+307
-22
lines changed

packages/api-v4/src/quotas/quotas.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,38 @@ export const getQuotaUsage = (type: QuotaType, id: string) =>
5252
setURL(`${BETA_API_ROOT}/${type}/quotas/${id}/usage`),
5353
setMethod('GET'),
5454
);
55+
56+
/**
57+
* getGlobalQuotas
58+
*
59+
* Returns a paginated list of global quotas for a particular service specified by `type`.
60+
*
61+
* This request can be filtered on `quota_name`, `service_name` and `scope`.
62+
*
63+
* @param type { QuotaType } retrieve quotas within this service type.
64+
*/
65+
export const getGlobalQuotas = (
66+
type: QuotaType,
67+
params: Params = {},
68+
filter: Filter = {},
69+
) =>
70+
Request<Page<Quota>>(
71+
setURL(`${BETA_API_ROOT}/${type}/global-quotas`),
72+
setMethod('GET'),
73+
setXFilter(filter),
74+
setParams(params),
75+
);
76+
77+
/**
78+
* getGlobalQuotaUsage
79+
*
80+
* Returns the usage for a single global quota within a particular service specified by `type`.
81+
*
82+
* @param type { QuotaType } retrieve a quota within this service type.
83+
* @param id { string } the quota ID to look up.
84+
*/
85+
export const getGlobalQuotaUsage = (type: QuotaType, id: string) =>
86+
Request<QuotaUsage>(
87+
setURL(`${BETA_API_ROOT}/${type}/global-quotas/${id}/usage`),
88+
setMethod('GET'),
89+
);

packages/manager/cypress/e2e/core/account/quotas-storage.spec.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,12 @@ describe('Quota workflow tests', () => {
100100
usage: Math.round(mockQuotas[2].quota_limit * 0.1),
101101
}),
102102
];
103+
103104
cy.wrap(selectedDomain).as('selectedDomain');
104105
cy.wrap(mockEndpoints).as('mockEndpoints');
105106
cy.wrap(mockQuotas).as('mockQuotas');
106107
cy.wrap(mockQuotaUsages).as('mockQuotaUsages');
108+
107109
mockGetObjectStorageQuotaUsages(
108110
selectedDomain,
109111
'bytes',
@@ -134,6 +136,7 @@ describe('Quota workflow tests', () => {
134136
},
135137
}).as('getFeatureFlags');
136138
});
139+
137140
it('Quotas and quota usages display properly', function () {
138141
cy.visitWithLogin('/account/quotas');
139142
cy.wait(['@getFeatureFlags', '@getObjectStorageEndpoints']);
@@ -332,9 +335,11 @@ describe('Quota workflow tests', () => {
332335
.should('be.visible')
333336
.click();
334337
cy.wait('@getQuotasError');
335-
cy.get('[data-qa-error-msg="true"]')
336-
.should('be.visible')
337-
.should('have.text', errorMsg);
338+
cy.get('[data-testid="endpoint-quotas-table-container"]').within(() => {
339+
cy.get('[data-qa-error-msg="true"]')
340+
.should('be.visible')
341+
.should('have.text', errorMsg);
342+
});
338343
});
339344
});
340345

@@ -508,9 +513,12 @@ describe('Quota workflow tests', () => {
508513
.should('be.visible')
509514
.click();
510515
cy.wait('@getQuotasError');
511-
cy.get('[data-qa-error-msg="true"]')
512-
.should('be.visible')
513-
.should('have.text', errorMsg);
516+
517+
cy.get('[data-testid="endpoint-quotas-table-container"]').within(() => {
518+
cy.get('[data-qa-error-msg="true"]')
519+
.should('be.visible')
520+
.should('have.text', errorMsg);
521+
});
514522
});
515523

516524
// this test executed in context of internal user, using mockApiInternalUser()
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React from 'react';
2+
3+
import { Table } from 'src/components/Table/Table';
4+
import { TableBody } from 'src/components/TableBody';
5+
import { TableCell } from 'src/components/TableCell/TableCell';
6+
import { TableHead } from 'src/components/TableHead';
7+
import { TableRow } from 'src/components/TableRow/TableRow';
8+
import { TableRowEmpty } from 'src/components/TableRowEmpty/TableRowEmpty';
9+
import { TableRowError } from 'src/components/TableRowError/TableRowError';
10+
import { TableRowLoading } from 'src/components/TableRowLoading/TableRowLoading';
11+
12+
import { useGetObjGlobalQuotasWithUsage } from '../hooks/useGetObjGlobalQuotasWithUsage';
13+
import { QUOTA_ROW_MIN_HEIGHT } from '../utils';
14+
import { GlobalQuotasTableRow } from './GlobalQuotasTableRow';
15+
16+
export const GlobalQuotasTable = () => {
17+
const {
18+
data: globalQuotasWithUsage,
19+
isFetching: isFetchingGlobalQuotas,
20+
isError: globalQuotasError,
21+
} = useGetObjGlobalQuotasWithUsage();
22+
23+
return (
24+
<Table
25+
data-testid="table-endpoint-global-quotas"
26+
sx={(theme) => ({
27+
marginTop: theme.spacingFunction(16),
28+
minWidth: theme.breakpoints.values.sm,
29+
})}
30+
>
31+
<TableHead>
32+
<TableRow>
33+
<TableCell sx={{ width: '25%' }}>Quota Name</TableCell>
34+
<TableCell sx={{ width: '30%' }}>Account Quota Value</TableCell>
35+
<TableCell sx={{ width: '35%' }}>Usage</TableCell>
36+
</TableRow>
37+
</TableHead>
38+
39+
<TableBody>
40+
{isFetchingGlobalQuotas ? (
41+
<TableRowLoading columns={3} sx={{ height: QUOTA_ROW_MIN_HEIGHT }} />
42+
) : globalQuotasError ? (
43+
<TableRowError
44+
colSpan={3}
45+
message="There was an error retrieving global object storage quotas."
46+
/>
47+
) : globalQuotasWithUsage.length === 0 ? (
48+
<TableRowEmpty
49+
colSpan={3}
50+
message="There is no data available for this service."
51+
sx={{ height: QUOTA_ROW_MIN_HEIGHT }}
52+
/>
53+
) : (
54+
globalQuotasWithUsage.map((globalQuota, index) => {
55+
return (
56+
<GlobalQuotasTableRow globalQuota={globalQuota} key={index} />
57+
);
58+
})
59+
)}
60+
</TableBody>
61+
</Table>
62+
);
63+
};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { Box, TooltipIcon, Typography } from '@linode/ui';
2+
import React from 'react';
3+
4+
import { QuotaUsageBar } from 'src/components/QuotaUsageBar/QuotaUsageBar';
5+
import { TableCell } from 'src/components/TableCell/TableCell';
6+
import { TableRow } from 'src/components/TableRow/TableRow';
7+
8+
import {
9+
convertResourceMetric,
10+
pluralizeMetric,
11+
QUOTA_ROW_MIN_HEIGHT,
12+
} from '../utils';
13+
14+
import type { Quota, QuotaUsage } from '@linode/api-v4';
15+
16+
interface GlobalQuotaWithUsage extends Quota {
17+
usage?: QuotaUsage;
18+
}
19+
interface Params {
20+
globalQuota: GlobalQuotaWithUsage;
21+
}
22+
23+
export const GlobalQuotasTableRow = ({ globalQuota }: Params) => {
24+
const { convertedLimit, convertedResourceMetric } = convertResourceMetric({
25+
initialResourceMetric: pluralizeMetric(
26+
globalQuota.quota_limit,
27+
globalQuota.resource_metric
28+
),
29+
initialUsage: globalQuota.usage?.usage ?? 0,
30+
initialLimit: globalQuota.quota_limit,
31+
});
32+
33+
return (
34+
<TableRow sx={{ height: QUOTA_ROW_MIN_HEIGHT }}>
35+
<TableCell>
36+
<Box alignItems="center" display="flex" flexWrap="nowrap">
37+
<Typography
38+
sx={{
39+
whiteSpace: 'nowrap',
40+
}}
41+
>
42+
{globalQuota.quota_name}
43+
</Typography>
44+
<TooltipIcon
45+
placement="top"
46+
status="info"
47+
sxTooltipIcon={{
48+
position: 'relative',
49+
top: -2,
50+
}}
51+
text={globalQuota.description}
52+
tooltipPosition="right"
53+
/>
54+
</Box>
55+
</TableCell>
56+
57+
<TableCell>
58+
{convertedLimit?.toLocaleString() ?? 'unknown'}{' '}
59+
{convertedResourceMetric}
60+
</TableCell>
61+
62+
<TableCell>
63+
<Box sx={{ maxWidth: '80%' }}>
64+
{globalQuota.usage?.usage ? (
65+
<QuotaUsageBar
66+
limit={globalQuota.quota_limit}
67+
resourceMetric={globalQuota.resource_metric}
68+
usage={globalQuota.usage.usage}
69+
/>
70+
) : (
71+
<Typography>n/a</Typography>
72+
)}
73+
</Box>
74+
</TableCell>
75+
</TableRow>
76+
);
77+
};

packages/manager/src/features/Account/Quotas/Quotas.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import * as React from 'react';
1313
import { DocumentTitleSegment } from 'src/components/DocumentTitle';
1414
import { Link } from 'src/components/Link';
1515

16-
import { QuotasTable } from './QuotasTable';
16+
import { GlobalQuotasTable } from './GlobalQuotasTable/GlobalQuotasTable';
17+
import { QuotasTable } from './QuotasTable/QuotasTable';
1718
import { useGetLocationsForQuotaService } from './utils';
1819

1920
import type { Quota } from '@linode/api-v4';
@@ -39,14 +40,26 @@ export const Quotas = () => {
3940
return (
4041
<>
4142
<DocumentTitleSegment segment="Quotas" />
43+
44+
<Paper
45+
sx={(theme: Theme) => ({
46+
marginTop: theme.spacingFunction(16),
47+
})}
48+
variant="outlined"
49+
>
50+
<Typography variant="h2">Object Storage: global</Typography>
51+
52+
<GlobalQuotasTable />
53+
</Paper>
54+
4255
<Paper
4356
sx={(theme: Theme) => ({
4457
marginTop: theme.spacingFunction(16),
4558
})}
4659
variant="outlined"
4760
>
4861
<Stack>
49-
<Typography variant="h2">Object Storage</Typography>
62+
<Typography variant="h2">Object Storage: per-endpoint</Typography>
5063
<Box sx={{ display: 'flex' }}>
5164
<Notice spacingTop={16} variant="info">
5265
<Typography>
@@ -105,7 +118,12 @@ export const Quotas = () => {
105118
</Link>
106119
.
107120
</Typography>
108-
<Stack direction="column" spacing={2}>
121+
122+
<Stack
123+
data-testid="endpoint-quotas-table-container"
124+
direction="column"
125+
spacing={2}
126+
>
109127
<QuotasTable
110128
selectedLocation={selectedLocation}
111129
selectedService={{

packages/manager/src/features/Account/Quotas/QuotasTable.test.tsx renamed to packages/manager/src/features/Account/Quotas/QuotasTable/QuotasTable.test.tsx

File renamed without changes.

packages/manager/src/features/Account/Quotas/QuotasTable.tsx renamed to packages/manager/src/features/Account/Quotas/QuotasTable/QuotasTable.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,14 @@ import { TableRowEmpty } from 'src/components/TableRowEmpty/TableRowEmpty';
1414
import { TableRowLoading } from 'src/components/TableRowLoading/TableRowLoading';
1515
import { usePaginationV2 } from 'src/hooks/usePaginationV2';
1616

17-
import { QuotasIncreaseForm } from './QuotasIncreaseForm';
17+
import { QuotasIncreaseForm } from '../QuotasIncreaseForm';
18+
import { getQuotasFilters, QUOTA_ROW_MIN_HEIGHT } from '../utils';
1819
import { QuotasTableRow } from './QuotasTableRow';
19-
import { getQuotasFilters } from './utils';
2020

2121
import type { Filter, Quota, QuotaType } from '@linode/api-v4';
2222
import type { SelectOption } from '@linode/ui';
2323
import type { AttachmentError } from 'src/features/Support/SupportTicketDetail/SupportTicketDetail';
2424

25-
const quotaRowMinHeight = 58;
26-
2725
interface QuotasTableProps {
2826
selectedLocation: null | SelectOption<Quota['region_applied']>;
2927
selectedService: SelectOption<QuotaType>;
@@ -127,19 +125,19 @@ export const QuotasTable = (props: QuotasTableProps) => {
127125
<TableRowLoading
128126
columns={4}
129127
rows={3}
130-
sx={{ height: quotaRowMinHeight }}
128+
sx={{ height: QUOTA_ROW_MIN_HEIGHT }}
131129
/>
132130
) : !selectedLocation ? (
133131
<TableRowEmpty
134132
colSpan={4}
135133
message="Apply filters above to see quotas and current usage."
136-
sx={{ height: quotaRowMinHeight }}
134+
sx={{ height: QUOTA_ROW_MIN_HEIGHT }}
137135
/>
138136
) : quotasWithUsage.length === 0 ? (
139137
<TableRowEmpty
140138
colSpan={4}
141139
message="There is no data available for this service and region."
142-
sx={{ height: quotaRowMinHeight }}
140+
sx={{ height: QUOTA_ROW_MIN_HEIGHT }}
143141
/>
144142
) : (
145143
quotasWithUsage.map((quota, index) => {

packages/manager/src/features/Account/Quotas/QuotasTableRow.tsx renamed to packages/manager/src/features/Account/Quotas/QuotasTable/QuotasTableRow.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ import { TableRow } from 'src/components/TableRow/TableRow';
99
import { useFlags } from 'src/hooks/useFlags';
1010
import { useIsAkamaiAccount } from 'src/hooks/useIsAkamaiAccount';
1111

12-
import { convertResourceMetric, getQuotaError, pluralizeMetric } from './utils';
12+
import {
13+
convertResourceMetric,
14+
getQuotaError,
15+
pluralizeMetric,
16+
QUOTA_ROW_MIN_HEIGHT,
17+
} from '../utils';
1318

1419
import type { Quota, QuotaUsage } from '@linode/api-v4';
1520
import type { UseQueryResult } from '@tanstack/react-query';
@@ -32,8 +37,6 @@ interface QuotasTableRowProps {
3237
setSupportModalOpen: (open: boolean) => void;
3338
}
3439

35-
const quotaRowMinHeight = 58;
36-
3740
export const QuotasTableRow = (props: QuotasTableRowProps) => {
3841
const {
3942
hasQuotaUsage,
@@ -77,7 +80,7 @@ export const QuotasTableRow = (props: QuotasTableRowProps) => {
7780
};
7881

7982
return (
80-
<TableRow key={quota.quota_id} sx={{ height: quotaRowMinHeight }}>
83+
<TableRow key={quota.quota_id} sx={{ height: QUOTA_ROW_MIN_HEIGHT }}>
8184
<TableCell>
8285
<Box alignItems="center" display="flex" flexWrap="nowrap">
8386
<Typography

0 commit comments

Comments
 (0)