diff --git a/public/app/api/clients/provisioning/index.ts b/public/app/api/clients/provisioning/index.ts index bbddc1ae363..c206d8611fa 100644 --- a/public/app/api/clients/provisioning/index.ts +++ b/public/app/api/clients/provisioning/index.ts @@ -3,18 +3,7 @@ import { isFetchError } from '@grafana/runtime'; import { notifyApp } from 'app/core/actions'; import { createSuccessNotification, createErrorNotification } from 'app/core/copy/appNotification'; -import { - generatedAPI, - JobSpec, - JobStatus, - RepositorySpec, - RepositoryStatus, - Job, - JobList, - Repository, - RepositoryList, - ErrorDetails, -} from './endpoints.gen'; +import { generatedAPI, JobSpec, JobStatus, RepositorySpec, RepositoryStatus, ErrorDetails } from './endpoints.gen'; import { createOnCacheEntryAdded } from './utils/createOnCacheEntryAdded'; export const provisioningAPI = generatedAPI.enhanceEndpoints({ @@ -26,16 +15,14 @@ export const provisioningAPI = generatedAPI.enhanceEndpoints({ url: `/jobs`, params: queryArg, }), - onCacheEntryAdded: createOnCacheEntryAdded('jobs'), + onCacheEntryAdded: createOnCacheEntryAdded('jobs'), }, listRepository: { query: ({ watch, ...queryArg }) => ({ url: `/repositories`, params: queryArg, }), - onCacheEntryAdded: createOnCacheEntryAdded( - 'repositories' - ), + onCacheEntryAdded: createOnCacheEntryAdded('repositories'), }, deleteRepository: { onQueryStarted: async (_, { queryFulfilled, dispatch }) => { diff --git a/public/app/api/clients/provisioning/utils/createOnCacheEntryAdded.ts b/public/app/api/clients/provisioning/utils/createOnCacheEntryAdded.ts index c3c4f38b8e1..c3c44ede267 100644 --- a/public/app/api/clients/provisioning/utils/createOnCacheEntryAdded.ts +++ b/public/app/api/clients/provisioning/utils/createOnCacheEntryAdded.ts @@ -1,20 +1,14 @@ import { Subscription } from 'rxjs'; -import { ScopedResourceClient } from '../../../../features/apiserver/client'; -import { ListOptions } from '../../../../features/apiserver/types'; -import { ListMeta, ObjectMeta } from '../endpoints.gen'; +import { ScopedResourceClient } from 'app/features/apiserver/client'; +import { ListOptions, GeneratedResourceList as ResourceList } from 'app/features/apiserver/types'; /** * Creates a cache entry handler for RTK Query that watches for changes to a resource * and updates the cache accordingly. */ -export function createOnCacheEntryAdded< - Spec, - Status, - T extends { spec?: Spec; status?: Status; metadata?: ObjectMeta }, - List extends { items?: T[]; metadata?: ListMeta }, ->(resourceName: string) { - return async function onCacheEntryAdded( +export function createOnCacheEntryAdded(resourceName: string) { + return async function onCacheEntryAdded>( arg: ListOptions | undefined, { updateCachedData, @@ -41,24 +35,24 @@ export function createOnCacheEntryAdded< // Wait for the initial query to resolve before proceeding const response = await cacheDataLoaded; const resourceVersion = response.data.metadata?.resourceVersion; + subscription = client.watch({ resourceVersion }).subscribe((event) => { updateCachedData((draft) => { if (!draft.items) { draft.items = []; } + // Find the item with the matching name const existingIndex = draft.items.findIndex((item) => item.metadata?.name === event.object.metadata.name); if (event.type === 'ADDED' && existingIndex === -1) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - draft.items.push(event.object as unknown as T); + draft.items.push(event.object); } else if (event.type === 'DELETED' && existingIndex !== -1) { // Remove the item if it exists draft.items.splice(existingIndex, 1); } else if (existingIndex !== -1) { // Could be ADDED or MODIFIED // Update the existing item if it exists - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - draft.items[existingIndex] = event.object as unknown as T; + draft.items[existingIndex] = event.object; } }); }); diff --git a/public/app/features/apiserver/guards.ts b/public/app/features/apiserver/guards.ts new file mode 100644 index 00000000000..e08bc76fc5d --- /dev/null +++ b/public/app/features/apiserver/guards.ts @@ -0,0 +1,26 @@ +import { Resource, ResourceList, GeneratedResource, GeneratedResourceList } from './types'; + +/** + * Type guard to check if a GeneratedResource has all required fields to be a Resource + */ +export function isResource( + generated: GeneratedResource +): generated is Resource { + return ( + !!generated.apiVersion && + !!generated.kind && + !!generated.metadata?.name && + !!generated.metadata?.resourceVersion && + !!generated.metadata?.creationTimestamp && + !!generated.spec + ); +} + +/** + * Type guard to check if a GeneratedResourceList has all required fields to be a ResourceList + */ +export function isResourceList( + generatedList: GeneratedResourceList +): generatedList is ResourceList { + return !!generatedList.metadata?.resourceVersion && Array.isArray(generatedList.items); +} diff --git a/public/app/features/apiserver/types.ts b/public/app/features/apiserver/types.ts index ebede3c24d0..58df8592274 100644 --- a/public/app/features/apiserver/types.ts +++ b/public/app/features/apiserver/types.ts @@ -257,3 +257,18 @@ export interface K8sAPIGroupList { kind: 'APIGroupList'; groups: K8sAPIGroup[]; } + +/** + * Generic types to match the generated k8s API types in the RTK query clients + */ +export interface GeneratedObjectMeta extends Partial {} +export interface GeneratedResource extends Partial> { + metadata?: GeneratedObjectMeta; + spec?: T; + status?: S; +} + +export interface GeneratedResourceList { + metadata?: Partial; + items?: Array>; +}