import type {CommitOffsetParams} from '../../store/reducers/partitions/types'; import type {PlanToSvgQueryParams} from '../../store/reducers/planToSvg'; import type {VDiskBlobIndexStatParams} from '../../store/reducers/vdisk/vdisk'; import type { AccessRightsUpdateRequest, AvailablePermissionsResponse, TMetaInfo, } from '../../types/api/acl'; import type {TQueryAutocomplete} from '../../types/api/autocomplete'; import type {CapabilitiesResponse} from '../../types/api/capabilities'; import type {TClusterInfo} from '../../types/api/cluster'; import type {SchemaPathParam} from '../../types/api/common'; import type {DescribeConsumerResult} from '../../types/api/consumer'; import type {FeatureFlagConfigs} from '../../types/api/featureFlags'; import type {HealthCheckAPIResponse} from '../../types/api/healthcheck'; import type {JsonHotKeysResponse} from '../../types/api/hotkeys'; import type {TNetInfo} from '../../types/api/netInfo'; import type {NodesRequestParams, TNodesInfo} from '../../types/api/nodes'; import type {TEvNodesInfo} from '../../types/api/nodesList'; import type {TEvPDiskStateResponse} from '../../types/api/pdisk'; import type {PeersRequestParams, TPeersResponse} from '../../types/api/peers'; import type { Actions, ErrorResponse, QueryAPIResponse, SendQueryParams, } from '../../types/api/query'; import type {JsonRenderRequestParams, JsonRenderResponse} from '../../types/api/render'; import type {DescribeReplicationResult} from '../../types/api/replication'; import type {TEvDescribeSchemeResult} from '../../types/api/schema'; import type {StorageStatsRequestParams, StorageStatsResponse} from '../../types/api/storage'; import type {TEvSystemStateResponse} from '../../types/api/systemState'; import type { TDomainKey, TEvTabletStateResponse, THiveInfoResponse, UnmergedTEvTabletStateResponse, } from '../../types/api/tablet'; import type {TTenantInfo, TTenants} from '../../types/api/tenant'; import type {DescribeTopicResult, TopicDataRequest, TopicDataResponse} from '../../types/api/topic'; import type {VDiskBlobIndexResponse} from '../../types/api/vdiskBlobIndex'; import type {TUserToken} from '../../types/api/whoami'; import type {TabletsApiRequestParams} from '../../types/store/tablets'; import type {Nullable} from '../../utils/typecheckers'; import type {AxiosOptions} from './base'; import {BaseYdbAPI} from './base'; export class ViewerAPI extends BaseYdbAPI { getClusterCapabilities({database}: {database?: string}) { return this.get(this.getPath('/viewer/capabilities'), {database}, {}); } getClusterInfo(clusterName?: string, {concurrentId, signal}: AxiosOptions = {}) { return this.get( this.getPath('/viewer/json/cluster'), { name: clusterName, tablets: true, }, {concurrentId: concurrentId || `getClusterInfo`, requestConfig: {signal}}, ); } /** id=. returns data about node that fullfills request */ getNodeInfo( {nodeId, database}: {nodeId?: string | number; database?: string}, {concurrentId, timeout, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/sysinfo?enums=true'), { node_id: nodeId, database, fields_required: -1, }, {concurrentId, requestConfig: {signal}, timeout}, ); } getTenants( { clusterName, metadataCache, storage = true, }: {clusterName?: string; metadataCache?: boolean; storage?: boolean}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/tenantinfo'), { tablets: false, storage, metadata_cache: metadataCache, cluster_name: clusterName, }, {concurrentId, requestConfig: {signal}}, ); } getTenantInfo({database}: {database: string}, {concurrentId, signal}: AxiosOptions = {}) { return this.get( this.getPath('/viewer/json/tenantinfo'), { database, path: database, tablets: false, storage: true, memory: true, }, {concurrentId, requestConfig: {signal}}, ); } getNodes( { type = 'any', tablets, database, tenant, fieldsRequired, filter, path, storage, ...params }: NodesRequestParams, {concurrentId, signal}: AxiosOptions = {}, ) { // This param determines whether we need VDisks to be returned // We need them only together with PDisks const isStorage = storage ?? fieldsRequired?.includes('PDisks'); const preparedFieldsRequired = Array.isArray(fieldsRequired) ? this.prepareArrayRequestParam(fieldsRequired) : fieldsRequired; return this.get( this.getPath('/viewer/json/nodes?enums=true'), { type, // Use tablets for backward compatibility even if fieldsRequired is passed tablets: tablets ?? fieldsRequired?.includes('Tablets'), // Do not send empty string filter: filter || undefined, // TODO: remove after remove tenant param database: database || tenant, fields_required: preparedFieldsRequired, path: this.getSchemaPath(path), storage: isStorage, ...params, }, {concurrentId, requestConfig: {signal}}, ); } getNodePeers( {nodeId, filter, ...params}: PeersRequestParams, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/peers'), { enums: true, node_id: nodeId, filter: filter || undefined, ...params, }, {concurrentId, requestConfig: {signal}}, ); } getTabletsInfo( {nodeId, path, database, filter}: TabletsApiRequestParams, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/tabletinfo'), { database, node_id: nodeId, path: this.getSchemaPath(path), enums: true, filter, }, {concurrentId, requestConfig: {signal}}, ); } getSchema( {path, database}: {path: SchemaPathParam; database: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get>( this.getPath('/viewer/json/describe'), { database, path: this.getSchemaPath(path), enums: true, backup: false, private: true, partition_config: false, partition_stats: false, partitioning_info: false, subs: 1, }, {concurrentId, requestConfig: {signal}}, ); } getDescribe( {path, database, timeout}: {path: SchemaPathParam; database: string; timeout?: number}, {concurrentId, signal}: AxiosOptions = {}, ) { const schemaPath = this.getSchemaPath(path); return this.get>( this.getPath('/viewer/json/describe'), { database, path: schemaPath, enums: true, partition_stats: false, subs: 0, }, { concurrentId: concurrentId || `getDescribe|${database}|${schemaPath}`, requestConfig: {signal}, timeout, }, ); } getSchemaAcl( {path, database, dialect}: {path: SchemaPathParam; database: string; dialect: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/acl'), { database, path: this.getSchemaPath(path), merge_rules: true, dialect, }, {concurrentId, requestConfig: {signal}}, ); } getAvailablePermissions( {path, database, dialect}: {path: SchemaPathParam; database: string; dialect: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/acl'), { database, path: this.getSchemaPath(path), merge_rules: true, dialect, list_permissions: true, }, {concurrentId, requestConfig: {signal}}, ); } updateAccessRights( { path, database, rights, dialect, }: { path: SchemaPathParam; database: string; rights: AccessRightsUpdateRequest; dialect: string; }, {concurrentId, signal}: AxiosOptions = {}, ) { return this.post( this.getPath('/viewer/json/acl'), rights, { database, path: this.getSchemaPath(path), merge_rules: true, dialect, }, {concurrentId, requestConfig: {signal}}, ); } getHeatmapData( {path, database}: {path: SchemaPathParam; database: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get>( this.getPath('/viewer/json/describe'), { database, path: this.getSchemaPath(path), enums: true, backup: false, children: false, partition_config: false, partition_stats: true, }, {concurrentId, requestConfig: {signal}}, ); } getNetwork( {path, database}: {path: SchemaPathParam; database: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/netinfo'), { enums: true, database, path: this.getSchemaPath(path), }, {concurrentId, requestConfig: {signal}}, ); } getReplication( {path, database}: {path: SchemaPathParam; database: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/describe_replication'), { enums: true, include_stats: true, database, path: this.getSchemaPath(path), }, {concurrentId, requestConfig: {signal}}, ); } getTopic( {path, database}: {path: SchemaPathParam; database: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/describe_topic'), { enums: true, include_stats: true, database, path: this.getSchemaPath(path), }, {concurrentId, requestConfig: {signal}}, ); } getTopicData(params: TopicDataRequest, {concurrentId, signal}: AxiosOptions = {}) { return this.get(this.getPath('/viewer/json/topic_data'), params, { concurrentId, requestConfig: {signal}, }); } getConsumer( {path, consumer, database}: {path: SchemaPathParam; consumer: string; database: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/describe_consumer'), { enums: true, include_stats: true, database, path: this.getSchemaPath(path), consumer, }, {concurrentId: concurrentId || 'getConsumer', requestConfig: {signal}}, ); } getHiveTablet( {id, hiveId}: {id: string; hiveId: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/hiveinfo'), { enums: true, hive_id: hiveId, tablet_id: id, }, {concurrentId, requestConfig: {signal}}, ); } getTablet( {id, database, followerId}: {id: string; database?: string; followerId?: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/tabletinfo'), { enums: true, database, filter: `(TabletId=${id};FollowerId=${followerId || 0})`, }, { concurrentId, requestConfig: {signal}, }, ); } getTabletHistory( {id, database}: {id: string; database?: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/tabletinfo'), { enums: true, merge: false, database, filter: `(TabletId=${id};State!=Dead)`, }, { concurrentId, requestConfig: {signal}, }, ); } getNodesList({database}: {database?: string}, {concurrentId, signal}: AxiosOptions = {}) { return this.get( this.getPath('/viewer/json/nodelist'), { enums: true, database, }, { concurrentId, requestConfig: {signal}, }, ); } getTenantsList({concurrentId, signal}: AxiosOptions = {}) { return this.get( this.getPath('/viewer/json/tenants'), { enums: true, state: 0, }, { concurrentId, requestConfig: {signal}, }, ); } sendQuery( params: SendQueryParams, {concurrentId, signal, withRetries}: AxiosOptions = {}, ) { const base64 = params.base64; return this.post | ErrorResponse | null>( this.getPath('/viewer/json/query'), {...params, base64}, {database: params.database, schema: 'multi', base64, timeout: params.timeout}, { concurrentId, timeout: params.timeout, requestConfig: { signal, 'axios-retry': {retries: withRetries ? this.DEFAULT_RETRIES_COUNT : 0}, }, headers: params.tracingLevel ? { 'X-Trace-Verbosity': params.tracingLevel, } : undefined, }, ); } getHotKeys( { path, database, enableSampling, }: {path: SchemaPathParam; database: string; enableSampling: boolean}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/hotkeys'), { database, path: this.getSchemaPath(path), enable_sampling: enableSampling, }, {concurrentId: concurrentId || 'getHotKeys', requestConfig: {signal}}, ); } getTabletDescribe( tenantId: TDomainKey, database?: string, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get>( this.getPath('/viewer/json/describe'), { schemeshard_id: tenantId?.SchemeShard, path_id: tenantId?.PathId, database, }, {concurrentId, requestConfig: {signal}}, ); } getStorageStats( { database, path, groupBy = 'path', everything, groups, tablets, media, }: StorageStatsRequestParams, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/storage_stats'), { database, path: this.getSchemaPath(path), group_by: groupBy, everything, groups, tablets, media, }, {concurrentId, requestConfig: {signal}}, ); } getChartData( {target, from, until, maxDataPoints, database}: JsonRenderRequestParams, {concurrentId, signal}: AxiosOptions = {}, ) { const requestString = `${target}&from=${from}&until=${until}&maxDataPoints=${maxDataPoints}&format=json`; return this.post( this.getPath(`/viewer/json/render?database=${database}`), requestString, {}, { concurrentId, headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, requestConfig: {signal}, }, ); } whoami({database}: {database?: string}) { return this.get(this.getPath('/viewer/json/whoami'), {database}); } autocomplete(params: {database: string; prefix?: string; limit?: number; table?: string[]}) { const {table, ...rest} = params; const tablesParam = table?.join(','); return this.get( this.getPath('/viewer/json/autocomplete'), {...rest, table: tablesParam}, {concurrentId: 'sql-autocomplete'}, ); } getFeatureFlags(database?: string, {concurrentId, signal}: AxiosOptions = {}) { return this.get( this.getPath('/viewer/feature_flags'), { database, }, {concurrentId, requestConfig: {signal}}, ); } getConfig(database?: string, {concurrentId, signal}: AxiosOptions = {}) { return this.get>( this.getPath('/viewer/config'), { database, }, {concurrentId, requestConfig: {signal}}, ); } getVDiskBlobIndexStat( {database, ...rest}: VDiskBlobIndexStatParams, {concurrentId, signal}: AxiosOptions = {}, ) { const params = 'vDiskId' in rest ? {vdisk_id: rest.vDiskId} : { node_id: rest.nodeId, pdisk_id: rest.pDiskId, vslot_id: rest.vDiskSlotId, }; return this.get( this.getPath('/vdisk/blobindexstat'), { ...params, database, }, {concurrentId, requestConfig: {signal}}, ); } getNodeWhiteboardPDiskInfo( { nodeId, pDiskId, database, }: {nodeId: string | number; pDiskId: string | number; database?: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/pdiskinfo?enums=true'), { filter: `(NodeId=${nodeId}${pDiskId ? `;PDiskId=${pDiskId}` : ''})`, database, }, {concurrentId, requestConfig: {signal}}, ); } planToSvg({database, plan}: PlanToSvgQueryParams, {signal}: {signal?: AbortSignal} = {}) { return this.post( this.getPath('/viewer/plan2svg'), plan, {database}, { requestConfig: { signal, responseType: 'text', headers: { Accept: 'image/svg+xml', }, }, }, ); } getHealthcheckInfo( { database, maxLevel, clusterName, }: {database: string; maxLevel?: number; clusterName?: string}, {concurrentId, signal}: AxiosOptions = {}, ) { return this.get( this.getPath('/viewer/json/healthcheck?merge_records=true', clusterName), {database, max_level: maxLevel}, {concurrentId, requestConfig: {signal}}, ); } commitOffset( {database, path, consumer, partitionId, offset}: CommitOffsetParams, {concurrentId, signal}: AxiosOptions = {}, ) { return this.post( this.getPath('/viewer/commit_offset'), { database, path: this.getSchemaPath(path), consumer, partition_id: partitionId, offset, }, {database}, {concurrentId, requestConfig: {signal}}, ); } }