Skip to content

new: STORIF-187 - Global quota usage table created.#13197

Merged
skulpok-akamai merged 4 commits intolinode:developfrom
dchyrva-akamai:feature/STORIF-187
Feb 4, 2026
Merged

new: STORIF-187 - Global quota usage table created.#13197
skulpok-akamai merged 4 commits intolinode:developfrom
dchyrva-akamai:feature/STORIF-187

Conversation

@dchyrva-akamai
Copy link
Contributor

Description 📝

Global quota usage table created.

Changes 🔄

  • Created new "Global Quota" tanstack queries.
  • Created new useGetObjGlobalQuotasWithUsage hook.
  • Created new GlobalQuotasTable component.

Preview 📷

image

How to test 🧪

  • Use the following branch for the apinext: dchyrva:feature/STORIF-185.
  • Run docker-compose up -d apinext.
  • Update .env file of the Cloud Manager to use local API instance.
  • Run pnpm dev
  • Open browser and navigate to /quotas page.
  • Observe new "Object Storage: global" quotas table.

Related PRs

apinext

Author Checklists

As an Author, to speed up the review process, I considered 🤔

👀 Doing a self review
❔ Our contribution guidelines
🤏 Splitting feature into small PRs
➕ Adding a changeset
🧪 Providing/improving test coverage
🔐 Removing all sensitive information from the code and PR description
🚩 Using a feature flag to protect the release
👣 Providing comprehensive reproduction steps
📑 Providing or updating our documentation
🕛 Scheduling a pair reviewing session
📱 Providing mobile support
♿ Providing accessibility support


  • I have read and considered all applicable items listed above.

As an Author, before moving this PR from Draft to Open, I confirmed ✅

  • All tests and CI checks are passing
  • TypeScript compilation succeeded without errors
  • Code passes all linting rules

@dchyrva-akamai dchyrva-akamai force-pushed the feature/STORIF-187 branch 3 times, most recently from e492df1 to 1c7f2aa Compare December 12, 2025 12:08
@dchyrva-akamai dchyrva-akamai marked this pull request as ready for review December 12, 2025 12:10
@dchyrva-akamai dchyrva-akamai requested a review from a team as a code owner December 12, 2025 12:10
@dchyrva-akamai dchyrva-akamai force-pushed the feature/STORIF-187 branch 3 times, most recently from 6c6c081 to 154edf3 Compare December 15, 2025 10:36
@dchyrva-akamai dchyrva-akamai requested a review from a team as a code owner December 15, 2025 10:36
@dchyrva-akamai dchyrva-akamai requested review from cliu-akamai and removed request for a team December 15, 2025 10:36
@dchyrva-akamai dchyrva-akamai force-pushed the feature/STORIF-187 branch 2 times, most recently from 6d4cd7e to dbcea0e Compare December 15, 2025 15:42
@dchyrva-akamai dchyrva-akamai force-pushed the feature/STORIF-187 branch 4 times, most recently from 67ab331 to ff1f699 Compare December 19, 2025 09:06
@github-actions
Copy link

github-actions bot commented Jan 4, 2026

This PR is stale because it has been open 15 days with no activity. Please attend to this PR or it will be closed in 5 days

setMethod('GET'),
setXFilter(filter),
setParams(params),
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need a "get all" method as well? Could we have more than 500 records?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently there is only one object in the list and I think there won't be more than 25 global quotas at all.
We ain't gonna need it =)

const globalQuotaIds =
globalQuotas?.data.map((quota) => quota.quota_id) ?? [];
const globalQuotaUsageQueries = useQueries({
queries: globalQuotaIds.map((quotaId) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How many quota IDs could you end up having to fetch in parallel? Asking because this could end up being a bad performance bottleneck if running queries for dozens (hundreds) of IDs.

Copy link
Contributor Author

@dchyrva-akamai dchyrva-akamai Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The decision to fetch quota usage like this was made by the Architect (from what I know), currently there is only one global quota in the object-storage, and I don't think there will ever be more than 25.

@github-actions github-actions bot removed the Stale label Jan 6, 2026
@dchyrva-akamai dchyrva-akamai force-pushed the feature/STORIF-187 branch 4 times, most recently from 5e50de3 to 6e7c8c5 Compare January 8, 2026 13:24
@dchyrva-akamai dchyrva-akamai force-pushed the feature/STORIF-187 branch 2 times, most recently from 1f66529 to d08d18a Compare January 28, 2026 12:23
paginationPreferenceKey: string,
collectionName: string,
enabled = true
) => {
const pagination = usePaginationV2({
Copy link
Contributor

@skulpok-akamai skulpok-akamai Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are few things that could be corrected in the usePaginationV2 configuration (like route should be /quotas, or queryParamsPrefix is missing), but actually I don't think we even need it at all. The purpose of usePaginationV2 is to store the table state in query params, so it can be restored when you refresh the page, paste the URL into a browser or use a bookmark. But it won't work for dimensional quotas - endpoint must be also selected in order to fetch the correct list of quotas and we don't have a mechanism to store it in the query params. Also, there is very slim chance that we ever have over 25 per-dimension or global quotas for a given feature. I think we can safely remove usePaginationV2. To make it work, Table would have to be wrapped in Paginate instead of using PaginationFooter. If it ever happens that there are a lot quotas per dimension, fetching usage would have to be optimized.

@abailly-akamai Do you see any reason to keep usePaginationV2?

export const useGetObjUsagePerEndpoint = (selectedLocation: string) => {
export const useGetQuotasWithUsage = (
selectedLocation: string,
selectedService: QuotaType,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The service is declarative in the code, not selected by the user (as opposed to location, though in the location case I don't like that the name implies how the "location" was determined). service or quotaService seem to be better names. Also, the QuotaType type now clashes with the quota_type field, which means something completely different, so we may consider renaming the type as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree about "QuotaType", It could be renamed into something more description (e.g ServiceType).

About selectedService/Location, I assume there will be added a select component later that will allow users to select between different services, hence the name.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, all service quotas will be displayed on a single page. That's the thing - you shouldn't assume here how the service will be provided, this is not the concern of this hook.

});

const hasSelectedLocation = Boolean(selectedLocation);
const isLocalQuotaScope = collectionName !== 'global-quotas';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect. You cannot determine if the quota is global or dimensional based on the collection name. For example, global quotas may be provided by /quotas, and multiple dimensional quotas may be provided by other paths (for example, quotas-per-region, quotas-per-instance, etc.). "/quotas" is the natural choice for global quotas when you have multiple quota dimensions.
The information whether quotas are per-dimension or global should be passed with a boolean prop.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, Block Storage quotas are in the design and there are 2 alternative solutions considered - either using single endpoint for both global and per-region quotas, or using /global-quotas for global quotas. If they go with /global-quotas, then indeed /global-quotas could be bound to global quotas - a perfect example of emergent architecture...
If you don't want expose both collectionName and, e.g., isGlobalScope in QuotaTable props, we could expose just isGlobalScope prop (so turn this around), and set collectionName to global-quotas in QuotasTable when isGlobalScope is true. I would still keep the collectionName for useQuotasQuery though to have some flexibility for the future.

data: quotasWithUsage,
quotas,
errorMessage: quotasErrorMessage,
queries: quotaUsageQueries,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem to be used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quotaUsageQueries - used inside the QuotasTableRow component to extract errors.

const SERVICE = 'object-storage';

export const useGetObjUsagePerEndpoint = (selectedLocation: string) => {
export const useGetQuotasWithUsage = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe simple useGetQuotas? There is no hook version without usage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to hide the complexity and the fact that usage uses a separate endpoint.

isError: isQuotasError,
isFetching: isFetchingQuotas,
} = useQuotasQuery(
SERVICE,
selectedService,
collectionName,
{
page: pagination.page,
page_size: pagination.pageSize,
Copy link
Contributor

@skulpok-akamai skulpok-akamai Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we drop usePaginationV2 we could simple use max allowed page size (i.e. 500) and use page 1. It's highly unlikely that we ever get more than 500 quotas per dimension per service.
We are filtering the data on the UI side, so using pagination wouldn't work anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, it's ok to deviate from the standards, and client side sort/filter/paginate your data in this case. I understand we're dealing with a small data set.

The only thing i would do is, even if you don't anticipate more 500 records, we're still fetching paginated data. I would still do a getAll query + hook. If anybody ever comes to your project the intent is much clearer that way.

It will still result in only fetching the first page, but again, showing the intent.

I would also comment the reason for doing so in code.

You can then decide to to client side paginate or no pagination at all, this is more so a UI/UX decision at that point.

@@ -32,8 +37,6 @@ interface QuotasTableRowProps {
setSupportModalOpen: (open: boolean) => void;
}

const quotaRowMinHeight = 58;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think passing quotaRowMinHeight as a prop instead of externalizing it to utils would be a cleaner approach.

@@ -56,8 +64,14 @@ export const useGetObjUsagePerEndpoint = (selectedLocation: string) => {

return {
data: quotaWithUsage,
quotas,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of returning quotas if quotaWithUsage (should be quotasWithUsage actually) already contains all the data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is needed for pagination in order to get total item count, I can either expose "totalItems" property instead of "quotas" or remove pagination completely.

@github-project-automation github-project-automation bot moved this from Review to Changes Requested in Cloud Manager Jan 30, 2026
@dchyrva-akamai dchyrva-akamai force-pushed the feature/STORIF-187 branch 9 times, most recently from 4dd09ff to d4fc327 Compare February 3, 2026 13:33
...quota,
usage: quotaUsageQueries?.[index]?.data,
})) ?? [],
[quotas, quotaUsageQueries]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might wanna look at that Eslint warning...

Copy link
Contributor

@skulpok-akamai skulpok-akamai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a feature flag for Global Quotas.

@linode-gh-bot
Copy link
Collaborator

Cloud Manager UI test results

🎉 866 passing tests on test run #40 ↗︎

❌ Failing✅ Passing↪️ Skipped🕐 Duration
0 Failing866 Passing11 Skipped35m 47s

Copy link
Contributor

@abailly-akamai abailly-akamai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good overall. I am lacking context about the feature & scope, but implementation looks clean and pagination removal is clear.

Approving to unblock, pending addressing some of the existing comments as needed, and validation by the storage team

selectedLocation: string,
selectedService: QuotaType,
collectionName: string,
enabled = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would strongly encourage to use named argument VS positional ones. It will make your instances much more readable. Past two arguments, it's usually good DX practice

service: QuotaType,
collection: string,
id: string,
enabled = true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment for the arguments in those queries. not blocking but improving DX can go a long way 🤷

@skulpok-akamai skulpok-akamai merged commit fe206af into linode:develop Feb 4, 2026
35 checks passed
@github-project-automation github-project-automation bot moved this from Changes Requested to Merged in Cloud Manager Feb 4, 2026
@dchyrva-akamai dchyrva-akamai deleted the feature/STORIF-187 branch February 4, 2026 13:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

4 participants