Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions apps/deploy-web/src/queries/useDeploymentQuery.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { getAllItems } from "@akashnetwork/http-sdk";
import type { QueryKey, UseQueryOptions } from "@tanstack/react-query";
import { useQuery } from "@tanstack/react-query";
import type { AxiosInstance } from "axios";

import { useServices } from "@src/context/ServicesProvider";
import type { DeploymentDto, RpcDeployment } from "@src/types/deployment";
import { ApiUrlService, loadWithPagination } from "@src/utils/apiUtils";
import { ApiUrlService } from "@src/utils/apiUtils";
import { deploymentToDto } from "@src/utils/deploymentDetailUtils";
import { QueryKeys } from "./queryKeys";

// Deployment list
async function getDeploymentList(chainApiHttpClient: AxiosInstance, address: string) {
if (!address) return [];

const deployments = await loadWithPagination<RpcDeployment[]>(ApiUrlService.deploymentList("", address), "deployments", 1000, chainApiHttpClient);
const deployments = await getAllItems<RpcDeployment>(async params => {
const response = await chainApiHttpClient.get(ApiUrlService.deploymentList("", address), {
params: { "pagination.limit": 1000, ...params }
});
return { items: response.data.deployments, pagination: response.data.pagination };
});

return deployments.map(d => deploymentToDto(d));
}
Expand Down
3 changes: 2 additions & 1 deletion apps/deploy-web/src/queries/useGrantsQuery.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ describe("useGrantsQuery", () => {

await vi.waitFor(() => {
expect(chainApiHttpClient.get).toHaveBeenCalledWith(
expect.stringContaining("/cosmos/feegrant/v1beta1/allowances/test-address?pagination.limit=1000&pagination.count_total=true")
expect.stringContaining("/cosmos/feegrant/v1beta1/allowances/test-address"),
expect.objectContaining({ params: expect.objectContaining({ "pagination.limit": 1000, "pagination.count_total": "true" }) })
);
expect(result.current.isSuccess).toBe(true);
expect(result.current.data).toEqual(mockData);
Expand Down
10 changes: 8 additions & 2 deletions apps/deploy-web/src/queries/useGrantsQuery.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { DepositDeploymentGrant } from "@akashnetwork/http-sdk";
import { getAllItems } from "@akashnetwork/http-sdk";
import type { UseQueryOptions } from "@tanstack/react-query";
import { useQuery } from "@tanstack/react-query";
import type { AxiosInstance } from "axios";

import { useServices } from "@src/context/ServicesProvider";
import type { AllowanceType, PaginatedAllowanceType, PaginatedGrantType } from "@src/types/grant";
import { ApiUrlService, loadWithPagination } from "@src/utils/apiUtils";
import { ApiUrlService } from "@src/utils/apiUtils";
import { QueryKeys } from "./queryKeys";

export function useGranterGrants(
Expand Down Expand Up @@ -54,7 +55,12 @@ export function useAllowancesIssued(
}

async function getAllowancesGranted(chainApiHttpClient: AxiosInstance, address: string) {
return await loadWithPagination<AllowanceType[]>(ApiUrlService.allowancesGranted("", address), "allowances", 1000, chainApiHttpClient);
return getAllItems<AllowanceType>(async params => {
const response = await chainApiHttpClient.get(ApiUrlService.allowancesGranted("", address), {
params: { "pagination.limit": 1000, ...params }
});
return { items: response.data.allowances, pagination: response.data.pagination };
});
}

export function useAllowancesGranted(address: string, options: Omit<UseQueryOptions<AllowanceType[]>, "queryKey" | "queryFn"> = {}) {
Expand Down
4 changes: 2 additions & 2 deletions apps/deploy-web/src/queries/useLeaseQuery.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ describe("useLeaseQuery", () => {
expect(result.current.isSuccess).toBe(true);
});

expect(chainApiHttpClient.get).toHaveBeenCalledWith(expect.stringContaining(`filters.dseq=${mockDeployment.dseq}`));
expect(chainApiHttpClient.get).toHaveBeenCalledWith(expect.stringContaining(`filters.dseq=${mockDeployment.dseq}`), expect.anything());
expect(result.current.data).toEqual([leaseToDto(mockLeases[0], mockDeployment)]);
});

Expand Down Expand Up @@ -228,7 +228,7 @@ describe("useLeaseQuery", () => {
await vi.waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(chainApiHttpClient.get).toHaveBeenCalledWith(expect.stringContaining("filters.owner=test-address"));
expect(chainApiHttpClient.get).toHaveBeenCalledWith(expect.stringContaining("filters.owner=test-address"), expect.anything());
expect(result.current.data).toEqual([leaseToDto(mockLeases[0], undefined as any)]);
});

Expand Down
24 changes: 16 additions & 8 deletions apps/deploy-web/src/queries/useLeaseQuery.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isHttpError } from "@akashnetwork/http-sdk";
import { getAllItems, isHttpError } from "@akashnetwork/http-sdk";
import type { UseQueryOptions } from "@tanstack/react-query";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import type { AxiosInstance } from "axios";
Expand All @@ -8,7 +8,7 @@ import { useProviderCredentials } from "@src/hooks/useProviderCredentials/usePro
import { useScopedFetchProviderUrl } from "@src/hooks/useScopedFetchProviderUrl";
import type { DeploymentDto, LeaseDto, RpcLease } from "@src/types/deployment";
import type { ApiProviderList } from "@src/types/provider";
import { ApiUrlService, loadWithPagination } from "@src/utils/apiUtils";
import { ApiUrlService } from "@src/utils/apiUtils";
import { leaseToDto } from "@src/utils/deploymentDetailUtils";
import { QueryKeys } from "./queryKeys";

Expand All @@ -18,10 +18,14 @@ async function getDeploymentLeases(chainApiHttpClient: AxiosInstance, address: s
return null;
}

const response = await loadWithPagination<RpcLease[]>(ApiUrlService.leaseList("", address, deployment?.dseq), "leases", 1000, chainApiHttpClient);
const leases = response.map(l => leaseToDto(l, deployment));
const leases = await getAllItems<RpcLease>(async params => {
const response = await chainApiHttpClient.get(ApiUrlService.leaseList("", address, deployment?.dseq), {
params: { "pagination.limit": 1000, ...params }
});
return { items: response.data.leases, pagination: response.data.pagination };
});

return leases;
return leases.map(l => leaseToDto(l, deployment));
}

export function useDeploymentLeaseList(
Expand Down Expand Up @@ -53,10 +57,14 @@ async function getAllLeases(chainApiHttpClient: AxiosInstance, address: string,
return null;
}

const response = await loadWithPagination<RpcLease[]>(ApiUrlService.leaseList("", address, deployment?.dseq), "leases", 1000, chainApiHttpClient);
const leases = response.map(l => leaseToDto(l, deployment));
const leases = await getAllItems<RpcLease>(async params => {
const response = await chainApiHttpClient.get(ApiUrlService.leaseList("", address, deployment?.dseq), {
params: { "pagination.limit": 1000, ...params }
});
return { items: response.data.leases, pagination: response.data.pagination };
});

return leases;
return leases.map(l => leaseToDto(l, deployment));
}

export function useAllLeases(address: string, options = {}) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { AuthzHttpService } from "@akashnetwork/http-sdk";
import { getAllItems } from "@akashnetwork/http-sdk";
import type { AxiosInstance } from "axios";

import type { RestApiBalancesResponseType } from "@src/types";
import type { Balances } from "@src/types/address";
import type { RpcDeployment } from "@src/types/deployment";
import { ApiUrlService, loadWithPagination } from "@src/utils/apiUtils";
import { ApiUrlService } from "@src/utils/apiUtils";
import { deploymentToDto } from "@src/utils/deploymentDetailUtils";

export class WalletBalancesService {
Expand All @@ -22,7 +23,12 @@ export class WalletBalancesService {
const [balanceResponse, deploymentGrants, activeDeploymentsResponse] = await Promise.all([
this.chainApiHttpClient.get<RestApiBalancesResponseType>(ApiUrlService.balance("", address)),
this.authzHttpService.getAllDepositDeploymentGrants({ grantee: address, limit: 1000 }),
loadWithPagination<RpcDeployment[]>(ApiUrlService.deploymentList("", address, true), "deployments", 1000, this.chainApiHttpClient)
getAllItems<RpcDeployment>(async params => {
const response = await this.chainApiHttpClient.get(ApiUrlService.deploymentList("", address, true), {
params: { "pagination.limit": 1000, ...params }
});
return { items: response.data.deployments, pagination: response.data.pagination };
})
]);

const deploymentGrantsPerDenom = deploymentGrants.reduce<Record<string, number>>((acc, grant) => {
Expand Down
41 changes: 0 additions & 41 deletions apps/deploy-web/src/utils/apiUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { AxiosInstance } from "axios";

import { services } from "@src/services/app-di-container/browser-di-container";
import networkStore from "@src/store/networkStore";
import { appendSearchParams } from "./urlUtils";
Expand Down Expand Up @@ -133,42 +131,3 @@ export class ApiUrlService {
return services.apiUrlService.getBaseApiUrlFor(networkStore.selectedNetworkId);
}
}

/**
* @deprecated use getAllItems utility from @akashnetwork/http-sdk
* TODO: implement proper pagination on clients
* Issue: https://github.com/akash-network/console/milestone/7
*/
export async function loadWithPagination<T>(baseUrl: string, dataKey: string, limit: number, httpClient: AxiosInstance) {
let items: T[] = [];
let nextKey: string | null = null;
// let callCount = 1;
// let totalCount = null;

do {
const _hasQueryParam = hasQueryParam(baseUrl);
let queryUrl = `${baseUrl}${_hasQueryParam ? "&" : "?"}pagination.limit=${limit}&pagination.count_total=true`;
if (nextKey) {
queryUrl += "&pagination.key=" + encodeURIComponent(nextKey);
}
// console.log(`Querying ${dataKey} [${callCount}] from : ${queryUrl}`);
const response = await httpClient.get(queryUrl);
const data = response.data;

// if (!nextKey) {
// totalCount = data.pagination.total;
// }

items = items.concat(data[dataKey]);
nextKey = data.pagination.next_key;
// callCount++;

// console.log(`Got ${items.length} of ${totalCount}`);
} while (nextKey);

return items.filter(item => item) as T;
}

function hasQueryParam(url: string) {
return /[?&]/gm.test(url);
}
11 changes: 9 additions & 2 deletions packages/http-sdk/src/deployment/deployment-http.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import z from "zod";

import { extractData } from "../http/http.service";
import type { HttpClient } from "../utils/httpClient";
import { loadWithPagination } from "../utils/pagination.utils";
import { getAllItems } from "../utils/pagination.utils";

const AttributeSchema = z.object({
key: z.string(),
Expand Down Expand Up @@ -167,7 +167,14 @@ export class DeploymentHttpService {
const defaultLimit = 1000;

if (!pagination) {
const allDeployments = await loadWithPagination<DeploymentInfo>(baseUrl, "deployments", defaultLimit, this.httpClient);
const allDeployments = await getAllItems<DeploymentInfo>(async params => {
const response = extractData(
await this.httpClient.get<DeploymentListResponse>(baseUrl, {
params: { "pagination.limit": defaultLimit, ...params }
})
Comment on lines +170 to +174
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify whether HttpClient/axios instance sets a global timeout
fd -i "httpClient.ts" packages --type f
fd -i "http.service.ts" packages --type f
rg -n --type=ts -C3 'timeout\s*:' packages/http-sdk/src

# Compare timeout usage in this service's pagination branches
rg -n --type=ts -C4 'getAllItems|deployments/list|timeout' packages/http-sdk/src/deployment/deployment-http.service.ts

Repository: akash-network/console

Length of output: 5871


🏁 Script executed:

cat -n packages/http-sdk/src/utils/httpClient.ts | head -100

Repository: akash-network/console

Length of output: 1279


🏁 Script executed:

rg -n "timeout|getAllItems" packages/http-sdk/src/utils/pagination.utils.ts -A 10 -B 2

Repository: akash-network/console

Length of output: 533


🏁 Script executed:

cat -n packages/http-sdk/src/utils/pagination.utils.ts

Repository: akash-network/console

Length of output: 1275


🏁 Script executed:

rg -n "constructor|httpClient\s*=" packages/http-sdk/src/deployment/deployment-http.service.ts -A 3 -B 3 | head -30

Repository: akash-network/console

Length of output: 322


Add timeout to the pagination fetch loop to prevent hangs.

The callback in getAllItems (lines 170-174) performs repeated HTTP calls without an explicit timeout. Since HttpClient has no global default timeout, this path can hang indefinitely if the API becomes unresponsive, stalling full-list retrievals. Other calls in the same file (lines 149, 206) use timeout: 30000—this branch should match that pattern.

Suggested patch
       const allDeployments = await getAllItems<DeploymentInfo>(async params => {
         const response = extractData(
           await this.httpClient.get<DeploymentListResponse>(baseUrl, {
-            params: { "pagination.limit": defaultLimit, ...params }
+            params: { "pagination.limit": defaultLimit, ...params },
+            timeout: 30000
           })
         );
         return { items: response.deployments, pagination: response.pagination };
       });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const allDeployments = await getAllItems<DeploymentInfo>(async params => {
const response = extractData(
await this.httpClient.get<DeploymentListResponse>(baseUrl, {
params: { "pagination.limit": defaultLimit, ...params }
})
const allDeployments = await getAllItems<DeploymentInfo>(async params => {
const response = extractData(
await this.httpClient.get<DeploymentListResponse>(baseUrl, {
params: { "pagination.limit": defaultLimit, ...params },
timeout: 30000
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/http-sdk/src/deployment/deployment-http.service.ts` around lines 170
- 174, The pagination callback passed to getAllItems (which fetches
DeploymentInfo using extractData and this.httpClient.get against baseUrl with
params including defaultLimit) lacks a request timeout; update the options
object passed to this.httpClient.get inside that callback to include timeout:
30000 (matching other calls in the file) so each paginated HTTP request will
time out after 30s and prevent hangs during full-list retrievals.

);
return { items: response.deployments, pagination: response.pagination };
});
return {
deployments: allDeployments,
pagination: {
Expand Down
38 changes: 0 additions & 38 deletions packages/http-sdk/src/utils/pagination.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,6 @@ import type { LoggerService } from "@akashnetwork/logging";

import { sdkLogger } from "./logger";

/**
* Helper function to load data with pagination
* @param baseUrl Base URL for the API request
* @param dataKey Key in the response that contains the data array
* @param limit Number of items per page
* @param httpClient HTTP client to use for requests
* @returns Array of items from all pages
*/
export async function loadWithPagination<T>(baseUrl: string, dataKey: string, limit: number, httpClient: { get: (url: string) => Promise<any> }): Promise<T[]> {
let items: T[] = [];
let nextKey: string | null = null;

do {
const hasQueryParam = /[?&]/gm.test(baseUrl);
let queryUrl = `${baseUrl}${hasQueryParam ? "&" : "?"}pagination.limit=${limit}&pagination.count_total=true`;
if (nextKey) {
queryUrl += "&pagination.key=" + encodeURIComponent(nextKey);
}

const response = await httpClient.get(queryUrl);
const data = response.data;

items = items.concat(data[dataKey]);
nextKey = data.pagination.next_key;
} while (nextKey);

return items.filter(item => item) as T[];
}

/**
* Helper function to check if a URL has query parameters
* @param url URL to check
* @returns Boolean indicating if the URL has query parameters
*/
export function hasQueryParam(url: string): boolean {
return new URL(url).searchParams.size > 0;
}

export async function getAllItems<T>(
getItems: (params: Record<string, string | number>) => Promise<{ items: T[]; pagination: { next_key: string | null } }>,
logger: Pick<LoggerService, "error"> = sdkLogger
Expand Down
Loading