diff --git a/ui/packages/@quent/client/src/api.ts b/ui/packages/@quent/client/src/api.ts index a5b3b8ba6..9c224225a 100644 --- a/ui/packages/@quent/client/src/api.ts +++ b/ui/packages/@quent/client/src/api.ts @@ -18,6 +18,8 @@ import type { EntityRef, Engine, TimelineConfig, + EntityListRequest, + EntityListResponse, } from '@quent/utils'; interface ApiFetchOptions { @@ -118,6 +120,22 @@ export async function fetchBulkTimelines( }); } +/** + * Fetch a ranked, paged list of a query's entities (longest resource-usage + * span first). Backs the long-entities Gantt view. + */ +export async function fetchEntityList( + engineId: string, + request: EntityListRequest +): Promise { + return apiFetch(`/engines/${engineId}/entities`, { + fetchOptions: { + method: 'POST', + body: JSON.stringify(request), + }, + }); +} + /** * Fetch the data-flow categorical timeline for a query (all operators in one * response). Resolves to `null` when the engine's analyzer does not implement diff --git a/ui/packages/@quent/client/src/entityList.ts b/ui/packages/@quent/client/src/entityList.ts new file mode 100644 index 000000000..e029204ab --- /dev/null +++ b/ui/packages/@quent/client/src/entityList.ts @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { queryOptions, useQuery } from '@tanstack/react-query'; +import type { + EntityListRequest, + EntityScope, + EntitySortKey, + OperatorFilter, + QueryFilter, + SortDir, +} from '@quent/utils'; +import { fetchEntityList } from './api'; +import { DEFAULT_STALE_TIME } from './constants'; + +interface EntityListParams { + engineId: string; + queryId: string; + /** Window bounds in seconds relative to the query epoch. */ + window: { start: number; end: number }; + /** Restrict to a single operator; `null` returns entities across all. */ + operatorId?: string | null; + /** Restrict entities to a resource / resource-group scope; `null` for all. */ + filter?: { scope?: EntityScope | null; entityTypeName?: string | null }; + /** Keep only entities whose longest usage span exceeds this (seconds). */ + minUsageSeconds?: number | null; + sortKey?: EntitySortKey; + sortDir?: SortDir; + /** Max entities to return; omit for the full (unpaged) list. */ + maxItems?: number | null; +} + +function buildRequest({ + queryId, + window, + operatorId = null, + filter, + minUsageSeconds = null, + sortKey = 'UsageDuration', + sortDir = 'Desc', + maxItems = null, +}: EntityListParams): EntityListRequest { + return { + entry: { + window, + filter: { + scope: filter?.scope ?? null, + entity_type_name: filter?.entityTypeName ?? null, + min_usage_s: minUsageSeconds, + }, + sort: { key: sortKey, dir: sortDir }, + page: maxItems != null ? { page: 0, max: maxItems } : null, + application: { operator_ids: operatorId == null ? [] : [operatorId] }, + }, + app_params: { query_id: queryId }, + }; +} + +export const entityListQueryOptions = ( + params: EntityListParams, + options?: { staleTime?: number; enabled?: boolean } +) => { + const request = buildRequest(params); + return queryOptions({ + queryKey: ['entityList', params.engineId, request], + queryFn: () => fetchEntityList(params.engineId, request), + staleTime: options?.staleTime ?? DEFAULT_STALE_TIME, + enabled: options?.enabled ?? true, + }); +}; +export const useEntityList = ( + params: EntityListParams, + options?: { staleTime?: number; enabled?: boolean } +) => useQuery(entityListQueryOptions(params, options)); diff --git a/ui/packages/@quent/client/src/index.ts b/ui/packages/@quent/client/src/index.ts index aef45f668..ed4cc2b9c 100644 --- a/ui/packages/@quent/client/src/index.ts +++ b/ui/packages/@quent/client/src/index.ts @@ -14,6 +14,7 @@ export { fetchSingleTimeline, fetchBulkTimelines, fetchDataFlow, + fetchEntityList, } from './api'; // queryOptions factories @@ -24,6 +25,7 @@ export { queriesQueryOptions } from './queries'; export { singleTimelineQueryOptions } from './timeline'; export { bulkTimelineQueryOptions } from './bulkTimelines'; export { dataFlowQueryOptions } from './dataFlow'; +export { entityListQueryOptions } from './entityList'; // Hooks export { useQueryBundle } from './queryBundle'; @@ -32,3 +34,4 @@ export { useQueryGroups } from './queryGroups'; export { useQueries } from './queries'; export { useTimeline } from './timeline'; export { useDataFlow } from './dataFlow'; +export { useEntityList } from './entityList'; diff --git a/ui/packages/@quent/utils/src/types/index.ts b/ui/packages/@quent/utils/src/types/index.ts index e7f0d0d87..a44a9c16a 100644 --- a/ui/packages/@quent/utils/src/types/index.ts +++ b/ui/packages/@quent/utils/src/types/index.ts @@ -17,7 +17,13 @@ export type { Edge } from '../../../../../generated/ts-bindings/Edge'; export type { Engine } from '../../../../../generated/ts-bindings/Engine'; export type { EngineImplementationAttributes } from '../../../../../generated/ts-bindings/EngineImplementationAttributes'; export type { EntityFilter } from '../../../../../generated/ts-bindings/EntityFilter'; +export type { EntityListEntry } from '../../../../../generated/ts-bindings/EntityListEntry'; +export type { EntityListFilter } from '../../../../../generated/ts-bindings/EntityListFilter'; +export type { EntityListRequest } from '../../../../../generated/ts-bindings/EntityListRequest'; +export type { EntityListResponse } from '../../../../../generated/ts-bindings/EntityListResponse'; export type { EntityRef } from '../../../../../generated/ts-bindings/EntityRef'; +export type { EntityScope } from '../../../../../generated/ts-bindings/EntityScope'; +export type { EntitySortKey } from '../../../../../generated/ts-bindings/EntitySortKey'; export type { FiniteStateMachine } from '../../../../../generated/ts-bindings/FiniteStateMachine'; export type { FsmStateTypeDecl } from '../../../../../generated/ts-bindings/FsmStateTypeDecl'; export type { FsmTransition } from '../../../../../generated/ts-bindings/FsmTransition'; @@ -28,6 +34,7 @@ export type { DynamicList } from '../../../../../generated/ts-bindings/DynamicLi export type { Operator } from '../../../../../generated/ts-bindings/Operator'; export type { OperatorFilter } from '../../../../../generated/ts-bindings/OperatorFilter'; export type { OperatorStatistics } from '../../../../../generated/ts-bindings/OperatorStatistics'; +export type { PageParams } from '../../../../../generated/ts-bindings/PageParams'; export type { Plan } from '../../../../../generated/ts-bindings/Plan'; export type { PlanTree } from '../../../../../generated/ts-bindings/PlanTree'; export type { Port } from '../../../../../generated/ts-bindings/Port'; @@ -52,9 +59,12 @@ export type { ResourceTree } from '../../../../../generated/ts-bindings/Resource export type { ResourceTypeDecl } from '../../../../../generated/ts-bindings/ResourceTypeDecl'; export type { SingleTimelineRequest } from '../../../../../generated/ts-bindings/SingleTimelineRequest'; export type { SingleTimelineResponse } from '../../../../../generated/ts-bindings/SingleTimelineResponse'; +export type { Sort } from '../../../../../generated/ts-bindings/Sort'; +export type { SortDir } from '../../../../../generated/ts-bindings/SortDir'; export type { SpanSec } from '../../../../../generated/ts-bindings/SpanSec'; export type { DynamicStruct } from '../../../../../generated/ts-bindings/DynamicStruct'; export type { TimelineConfig } from '../../../../../generated/ts-bindings/TimelineConfig'; export type { TimelineRequest } from '../../../../../generated/ts-bindings/TimelineRequest'; +export type { TimeWindow } from '../../../../../generated/ts-bindings/TimeWindow'; export type { DynamicValue } from '../../../../../generated/ts-bindings/DynamicValue'; export type { Worker } from '../../../../../generated/ts-bindings/Worker';