diff --git a/packages/api-v4/src/quotas/types.ts b/packages/api-v4/src/quotas/types.ts index fe094430757..c21a9beff78 100644 --- a/packages/api-v4/src/quotas/types.ts +++ b/packages/api-v4/src/quotas/types.ts @@ -1,5 +1,18 @@ import type { ObjectStorageEndpointTypes } from 'src/object-storage'; import type { Region } from 'src/regions'; + +export enum QuotaResourceMetrics { + BUCKET = 'bucket', + BYTE = 'byte', + BYTE_PER_SECOND = 'byte_per_second', + CLUSTER = 'cluster', + CPU = 'CPU', + GPU = 'GPU', + OBJECT = 'object', + REQUEST = 'request', + VPU = 'VPU', +} + /** * A Quota is a service used limit that is rated based on service metrics such * as vCPUs used, instances or storage size. @@ -54,7 +67,7 @@ export interface Quota { /** * The unit of measurement for this service limit. */ - resource_metric: string; + resource_metric: QuotaResourceMetrics; /** * The S3 endpoint URL to which this limit applies. diff --git a/packages/manager/.changeset/pr-13447-added-1773052533338.md b/packages/manager/.changeset/pr-13447-added-1773052533338.md new file mode 100644 index 00000000000..f2e06b74b95 --- /dev/null +++ b/packages/manager/.changeset/pr-13447-added-1773052533338.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Added +--- + +Quota resource metrics enum ([#13447](https://github.com/linode/manager/pull/13447)) diff --git a/packages/manager/cypress/e2e/core/account/quotas-storage.spec.ts b/packages/manager/cypress/e2e/core/account/quotas-storage.spec.ts index 411e0e157e2..4962af2027d 100644 --- a/packages/manager/cypress/e2e/core/account/quotas-storage.spec.ts +++ b/packages/manager/cypress/e2e/core/account/quotas-storage.spec.ts @@ -1,3 +1,4 @@ +import { QuotaResourceMetrics } from '@linode/api-v4'; import { regionFactory } from '@linode/utilities'; import { profileFactory } from '@linode/utilities'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; @@ -74,7 +75,7 @@ const mockQuotas = [ endpoint_type: mockSelectedEndpoint.endpoint_type, quota_limit: 10, quota_name: randomLabel(15), - resource_metric: 'byte', + resource_metric: QuotaResourceMetrics.BYTE, s3_endpoint: selectedDomain, }), quotaFactory.build({ @@ -84,7 +85,7 @@ const mockQuotas = [ endpoint_type: mockSelectedEndpoint.endpoint_type, quota_limit: 78, quota_name: randomLabel(15), - resource_metric: 'bucket', + resource_metric: QuotaResourceMetrics.BUCKET, s3_endpoint: selectedDomain, }), quotaFactory.build({ @@ -94,7 +95,7 @@ const mockQuotas = [ endpoint_type: mockSelectedEndpoint.endpoint_type, quota_limit: 400, quota_name: randomLabel(15), - resource_metric: 'object', + resource_metric: QuotaResourceMetrics.OBJECT, s3_endpoint: selectedDomain, }), ]; @@ -229,7 +230,7 @@ describe('Quota workflow tests', () => { endpoint_type: updatedEndpoint.endpoint_type, quota_limit: 20, quota_name: randomLabel(15), - resource_metric: 'byte', + resource_metric: QuotaResourceMetrics.BYTE, s3_endpoint: updatedDomain, }), quotaFactory.build({ @@ -239,7 +240,7 @@ describe('Quota workflow tests', () => { endpoint_type: updatedEndpoint.endpoint_type, quota_limit: 122, quota_name: randomLabel(15), - resource_metric: 'bucket', + resource_metric: QuotaResourceMetrics.BUCKET, s3_endpoint: updatedDomain, }), quotaFactory.build({ @@ -249,7 +250,7 @@ describe('Quota workflow tests', () => { endpoint_type: updatedEndpoint.endpoint_type, quota_limit: 450, quota_name: randomLabel(15), - resource_metric: 'object', + resource_metric: QuotaResourceMetrics.OBJECT, s3_endpoint: updatedDomain, }), ]; diff --git a/packages/manager/cypress/e2e/core/objectStorage/object-storage-summary-page.spec.ts b/packages/manager/cypress/e2e/core/objectStorage/object-storage-summary-page.spec.ts index af828a4a796..49758107569 100644 --- a/packages/manager/cypress/e2e/core/objectStorage/object-storage-summary-page.spec.ts +++ b/packages/manager/cypress/e2e/core/objectStorage/object-storage-summary-page.spec.ts @@ -1,3 +1,4 @@ +import { QuotaResourceMetrics } from '@linode/api-v4'; import { regionFactory } from '@linode/utilities'; import { authenticate } from 'support/api/authentication'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; @@ -58,7 +59,7 @@ const mockQuotas = [ endpoint_type: mockSelectedEndpoint.endpoint_type, quota_limit: 10, quota_name: 'Total Capacity', - resource_metric: 'byte', + resource_metric: QuotaResourceMetrics.BYTE, s3_endpoint: selectedDomain, }), quotaFactory.build({ @@ -68,7 +69,7 @@ const mockQuotas = [ endpoint_type: mockSelectedEndpoint.endpoint_type, quota_limit: 78, quota_name: 'Number of Objects', - resource_metric: 'bucket', + resource_metric: QuotaResourceMetrics.BUCKET, s3_endpoint: selectedDomain, }), quotaFactory.build({ @@ -78,7 +79,7 @@ const mockQuotas = [ endpoint_type: mockSelectedEndpoint.endpoint_type, quota_limit: 400, quota_name: 'Number of Buckets', - resource_metric: 'object', + resource_metric: QuotaResourceMetrics.OBJECT, s3_endpoint: selectedDomain, }), ]; diff --git a/packages/manager/src/components/QuotaUsageBar/QuotaUsageBar.test.tsx b/packages/manager/src/components/QuotaUsageBar/QuotaUsageBar.test.tsx index 1eb39ebc1bc..51c230bdc35 100644 --- a/packages/manager/src/components/QuotaUsageBar/QuotaUsageBar.test.tsx +++ b/packages/manager/src/components/QuotaUsageBar/QuotaUsageBar.test.tsx @@ -1,3 +1,4 @@ +import { QuotaResourceMetrics } from '@linode/api-v4'; import React from 'react'; import { renderWithTheme } from 'src/utilities/testHelpers'; @@ -43,7 +44,11 @@ describe('QuotaUsageBanner', () => { 'should display correct byte quota usage text for $usage bytes used out of $limit bytes', ({ usage, limit, expectedText }) => { const { getByText } = renderWithTheme( - + ); const quotaUsageText = getByText(expectedText); expect(quotaUsageText).toBeVisible(); diff --git a/packages/manager/src/components/QuotaUsageBar/QuotaUsageBar.tsx b/packages/manager/src/components/QuotaUsageBar/QuotaUsageBar.tsx index 76228724299..e5acc41f6cc 100644 --- a/packages/manager/src/components/QuotaUsageBar/QuotaUsageBar.tsx +++ b/packages/manager/src/components/QuotaUsageBar/QuotaUsageBar.tsx @@ -2,14 +2,13 @@ import { Typography, useTheme } from '@linode/ui'; import * as React from 'react'; import { BarPercent } from 'src/components/BarPercent'; -import { - convertResourceMetric, - pluralizeMetric, -} from 'src/features/Account/Quotas/utils'; +import { convertResourceMetric } from 'src/features/Account/Quotas/utils'; + +import type { QuotaResourceMetrics } from '@linode/api-v4'; interface Props { limit: number; - resourceMetric: string; + resourceMetric: QuotaResourceMetrics; usage: number; } @@ -18,7 +17,7 @@ export const QuotaUsageBar = ({ limit, usage, resourceMetric }: Props) => { const { convertedUsage, convertedLimit, convertedResourceMetric } = convertResourceMetric({ - initialResourceMetric: pluralizeMetric(limit, resourceMetric), + initialResourceMetric: resourceMetric, initialUsage: usage, initialLimit: limit, }); diff --git a/packages/manager/src/factories/quotas.ts b/packages/manager/src/factories/quotas.ts index d3be1d0f7bd..50d2d2dbc3d 100644 --- a/packages/manager/src/factories/quotas.ts +++ b/packages/manager/src/factories/quotas.ts @@ -1,3 +1,4 @@ +import { QuotaResourceMetrics } from '@linode/api-v4/lib/quotas/types'; import { Factory } from '@linode/utilities'; import type { Quota, QuotaUsage } from '@linode/api-v4/lib/quotas/types'; @@ -9,7 +10,7 @@ export const quotaFactory = Factory.Sync.makeFactory({ quota_name: 'Linode Dedicated vCPUs', quota_type: 'linode-dedicated-cpus', region_applied: 'us-east', - resource_metric: 'CPU', + resource_metric: QuotaResourceMetrics.CPU, has_usage: true, }); diff --git a/packages/manager/src/features/Account/Quotas/QuotasTable/QuotasTable.test.tsx b/packages/manager/src/features/Account/Quotas/QuotasTable/QuotasTable.test.tsx index 85b5c7da24f..7b632367ace 100644 --- a/packages/manager/src/features/Account/Quotas/QuotasTable/QuotasTable.test.tsx +++ b/packages/manager/src/features/Account/Quotas/QuotasTable/QuotasTable.test.tsx @@ -1,3 +1,4 @@ +import { QuotaResourceMetrics } from '@linode/api-v4'; import { waitFor } from '@testing-library/react'; import * as React from 'react'; @@ -131,7 +132,7 @@ describe('QuotasTable', () => { 'Current total ingress bandwidth per account, per endpoint', quota_limit: 1250000000, quota_type: 'obj-total-ingress-throughput', - resource_metric: 'byte_per_second', + resource_metric: QuotaResourceMetrics.BYTE_PER_SECOND, has_usage: false, }), ], diff --git a/packages/manager/src/features/Account/Quotas/QuotasTable/QuotasTableRow.tsx b/packages/manager/src/features/Account/Quotas/QuotasTable/QuotasTableRow.tsx index f9e638533ec..be75ef10bb5 100644 --- a/packages/manager/src/features/Account/Quotas/QuotasTable/QuotasTableRow.tsx +++ b/packages/manager/src/features/Account/Quotas/QuotasTable/QuotasTableRow.tsx @@ -9,11 +9,7 @@ import { TableRow } from 'src/components/TableRow/TableRow'; import { useFlags } from 'src/hooks/useFlags'; import { useIsAkamaiAccount } from 'src/hooks/useIsAkamaiAccount'; -import { - convertResourceMetric, - getQuotaError, - pluralizeMetric, -} from '../utils'; +import { convertResourceMetric, getQuotaError } from '../utils'; import type { QuotaWithUsage } from '../utils'; import type { Quota, QuotaUsage } from '@linode/api-v4'; @@ -58,10 +54,7 @@ export const QuotasTableRow = (props: QuotasTableRowProps) => { isAkamaiAccount); const { convertedLimit, convertedResourceMetric } = convertResourceMetric({ - initialResourceMetric: pluralizeMetric( - quota.quota_limit, - quota.resource_metric - ), + initialResourceMetric: quota.resource_metric, initialUsage: quota.usage?.usage ?? 0, initialLimit: quota.quota_limit, }); diff --git a/packages/manager/src/features/Account/Quotas/utils.test.tsx b/packages/manager/src/features/Account/Quotas/utils.test.tsx index 0015e35cbe3..d4ad5f3d3de 100644 --- a/packages/manager/src/features/Account/Quotas/utils.test.tsx +++ b/packages/manager/src/features/Account/Quotas/utils.test.tsx @@ -1,3 +1,4 @@ +import { QuotaResourceMetrics } from '@linode/api-v4'; import { profileFactory } from '@linode/utilities'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { renderHook } from '@testing-library/react'; @@ -133,7 +134,7 @@ describe('useGetLocationsForQuotaService', () => { describe('convertResourceMetric', () => { it('should convert the resource metric to a human readable format', () => { - const resourceMetric = 'byte'; + const resourceMetric = QuotaResourceMetrics.BYTE; const usage = 1e6; const limit = 1e8; @@ -154,25 +155,16 @@ describe('convertResourceMetric', () => { describe('pluralizeMetric', () => { it('should not pluralize if the value is 1', () => { const value = 1; - const unit = 'CPU'; + const unit = QuotaResourceMetrics.CPU; const result = pluralizeMetric(value, unit); expect(result).toEqual('CPU'); }); - it('should not pluralize the resource metric if the unit is byte', () => { + it('should pluralize the resource metric if the value is greater than 1', () => { const value = 100; - const unit = 'byte'; - - const result = pluralizeMetric(value, unit); - - expect(result).toEqual('byte'); - }); - - it('should pluralize the resource metric if the unit is not byte', () => { - const value = 100; - const unit = 'CPU'; + const unit = QuotaResourceMetrics.CPU; const result = pluralizeMetric(value, unit); diff --git a/packages/manager/src/features/Account/Quotas/utils.ts b/packages/manager/src/features/Account/Quotas/utils.ts index 4c926cfbabe..dad407d1df8 100644 --- a/packages/manager/src/features/Account/Quotas/utils.ts +++ b/packages/manager/src/features/Account/Quotas/utils.ts @@ -1,3 +1,4 @@ +import { QuotaResourceMetrics } from '@linode/api-v4'; import { useRegionsQuery } from '@linode/queries'; import { capitalize, readableBytes } from '@linode/utilities'; import { object, string } from 'yup'; @@ -217,7 +218,7 @@ export const getQuotaIncreaseMessage = ({ interface ConvertResourceMetricProps { initialLimit: number; - initialResourceMetric: string; + initialResourceMetric: QuotaResourceMetrics; initialUsage: number; } @@ -233,34 +234,38 @@ export const convertResourceMetric = ({ convertedResourceMetric: string; convertedUsage: number; } => { - if (initialResourceMetric === 'byte') { - const limitReadable = readableBytes(initialLimit); + switch (initialResourceMetric) { + case QuotaResourceMetrics.BYTE: { + const limitReadable = readableBytes(initialLimit); - return { - convertedUsage: readableBytes(initialUsage, { - unit: limitReadable.unit, - }).value, - convertedResourceMetric: capitalize(limitReadable.unit), - convertedLimit: limitReadable.value, - }; - } - - if (initialResourceMetric === 'byte_per_second') { - return { - convertedUsage: 0, - convertedResourceMetric: 'Gbps', - convertedLimit: readableBytes(initialLimit * 8, { - unit: 'GB', - base10: true, - }).value, - }; + return { + convertedUsage: readableBytes(initialUsage, { + unit: limitReadable.unit, + }).value, + convertedLimit: limitReadable.value, + convertedResourceMetric: capitalize(limitReadable.unit), + }; + } + case QuotaResourceMetrics.BYTE_PER_SECOND: { + return { + convertedUsage: 0, + convertedResourceMetric: 'Gbps', + convertedLimit: readableBytes(initialLimit * 8, { + unit: 'GB', + base10: true, + }).value, + }; + } + default: { + return { + convertedUsage: initialUsage, + convertedLimit: initialLimit, + convertedResourceMetric: capitalize( + pluralizeMetric(initialLimit, initialResourceMetric) + ), + }; + } } - - return { - convertedUsage: initialUsage, - convertedLimit: initialLimit, - convertedResourceMetric: capitalize(initialResourceMetric), - }; }; /** @@ -270,16 +275,11 @@ export const convertResourceMetric = ({ * * Note: the value should be the raw values in bytes, not an existing conversion */ -export const pluralizeMetric = (value: number, unit: string) => { - if (unit === 'byte_per_second') { - return unit; - } - - if (unit !== 'byte') { - return value > 1 ? `${unit}s` : unit; - } - - return unit; +export const pluralizeMetric = ( + value: number, + unit: QuotaResourceMetrics +): string => { + return value > 1 ? `${unit}s` : unit; }; export const getQuotaIncreaseFormSchema = (currentLimit: number) => diff --git a/packages/manager/src/features/ObjectStorage/SummaryLanding/Partials/EndpointSummaryRow.test.tsx b/packages/manager/src/features/ObjectStorage/SummaryLanding/Partials/EndpointSummaryRow.test.tsx index ce5e370de29..dfa16bde5f8 100644 --- a/packages/manager/src/features/ObjectStorage/SummaryLanding/Partials/EndpointSummaryRow.test.tsx +++ b/packages/manager/src/features/ObjectStorage/SummaryLanding/Partials/EndpointSummaryRow.test.tsx @@ -1,3 +1,4 @@ +import { QuotaResourceMetrics } from '@linode/api-v4'; import * as React from 'react'; import { quotaFactory, quotaUsageFactory } from 'src/factories/quotas'; @@ -45,7 +46,7 @@ const quotasMock = [ s3_endpoint: testEndpoint, description: 'Current number of buckets per account, per endpoint', quota_limit: 10, - resource_metric: 'bucket', + resource_metric: QuotaResourceMetrics.BUCKET, }), quotaFactory.build({ quota_id: `obj-bytes-${testEndpoint}`, @@ -55,7 +56,7 @@ const quotasMock = [ s3_endpoint: testEndpoint, description: 'Current total capacity per account, per endpoint', quota_limit: 2048, - resource_metric: 'byte', + resource_metric: QuotaResourceMetrics.BYTE, }), quotaFactory.build({ quota_id: `obj-objects-${testEndpoint}`, @@ -65,7 +66,7 @@ const quotasMock = [ s3_endpoint: testEndpoint, description: 'Current number of objects per account, per endpoint', quota_limit: 10, - resource_metric: 'object', + resource_metric: QuotaResourceMetrics.OBJECT, }), ]; diff --git a/packages/manager/src/mocks/presets/crud/handlers/quotas.ts b/packages/manager/src/mocks/presets/crud/handlers/quotas.ts index 7bbcd0ce3b8..b6d3b8510c3 100644 --- a/packages/manager/src/mocks/presets/crud/handlers/quotas.ts +++ b/packages/manager/src/mocks/presets/crud/handlers/quotas.ts @@ -1,3 +1,4 @@ +import { QuotaResourceMetrics } from '@linode/api-v4'; import { pickRandom, regions } from '@linode/utilities'; import { http } from 'msw'; @@ -30,7 +31,7 @@ const mockQuotas: Record = { quota_limit: 50, quota_name: 'Dedicated CPU', region_applied: region.id, - resource_metric: 'CPU', + resource_metric: QuotaResourceMetrics.CPU, }) ), ...regions.map((region) => @@ -40,7 +41,7 @@ const mockQuotas: Record = { quota_limit: 100, quota_name: 'Shared CPU', region_applied: region.id, - resource_metric: 'CPU', + resource_metric: QuotaResourceMetrics.CPU, }) ), ...regions.map((region) => @@ -49,7 +50,7 @@ const mockQuotas: Record = { quota_limit: 25, quota_name: 'GPU', region_applied: region.id, - resource_metric: 'GPU', + resource_metric: QuotaResourceMetrics.GPU, }) ), ...regions.map((region) => @@ -58,7 +59,7 @@ const mockQuotas: Record = { quota_limit: 10, quota_name: 'VPU', region_applied: region.id, - resource_metric: 'VPU', + resource_metric: QuotaResourceMetrics.VPU, }) ), ...regions.map((region) => @@ -68,7 +69,7 @@ const mockQuotas: Record = { quota_limit: 15, quota_name: 'High Memory', region_applied: region.id, - resource_metric: 'CPU', + resource_metric: QuotaResourceMetrics.CPU, }) ), ], @@ -78,7 +79,7 @@ const mockQuotas: Record = { quota_limit: 50, quota_name: 'Total number of Clusters', region_applied: region.id, - resource_metric: 'cluster', + resource_metric: QuotaResourceMetrics.CLUSTER, }) ), ], @@ -88,7 +89,7 @@ const mockQuotas: Record = { endpoint_type: 'E0', quota_limit: 1_000_000_000_000_000, // a petabyte quota_name: 'Total Capacity', - resource_metric: 'byte', + resource_metric: QuotaResourceMetrics.BYTE, s3_endpoint: 'us-east-1.linodeobjects.com', }), quotaFactory.build({ @@ -97,7 +98,7 @@ const mockQuotas: Record = { endpoint_type: 'E0', quota_limit: 100, quota_name: 'Number of Buckets', - resource_metric: 'bucket', + resource_metric: QuotaResourceMetrics.BUCKET, s3_endpoint: 'us-west-1.linodeobjects.com', }), quotaFactory.build({ @@ -105,7 +106,7 @@ const mockQuotas: Record = { endpoint_type: 'E3', quota_limit: 10_000_000, quota_name: 'Number of Objects', - resource_metric: 'object', + resource_metric: QuotaResourceMetrics.OBJECT, s3_endpoint: 'br-gru-1.linodeobjects.com', }), ], diff --git a/packages/manager/src/mocks/serverHandlers.ts b/packages/manager/src/mocks/serverHandlers.ts index fb44b19121c..4d8ce71f581 100644 --- a/packages/manager/src/mocks/serverHandlers.ts +++ b/packages/manager/src/mocks/serverHandlers.ts @@ -149,6 +149,8 @@ import type { PathParams } from 'msw'; const getRandomWholeNumber = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1) + min); +import { QuotaResourceMetrics } from '@linode/api-v4'; + import { accountEntityFactory } from 'src/factories/accountEntities'; import { accountRolesFactory } from 'src/factories/accountRoles'; import { trustedDeviceFactory } from 'src/factories/devices'; @@ -1813,7 +1815,7 @@ export const handlers = [ endpoint_type: 'E0', quota_limit: 1_000_000_000_000_000, quota_name: 'Total Capacity', - resource_metric: 'byte', + resource_metric: QuotaResourceMetrics.BYTE, s3_endpoint: 'endpoint1', }), ];