Provisioning: Refactor createOnCacheEntryAdded (#105228)

* Provisioning: Refactor createOnCacheEntryAdded

* Use custom list resource type

* add generated types and transformers

* Switch to type guards

* Do not create new array
This commit is contained in:
Alex Khomenko
2025-05-19 13:23:23 +03:00
committed by GitHub
parent c58ac15031
commit 15c903fa5c
4 changed files with 52 additions and 30 deletions
+3 -16
View File
@@ -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<JobSpec, JobStatus, Job, JobList>('jobs'),
onCacheEntryAdded: createOnCacheEntryAdded<JobSpec, JobStatus>('jobs'),
},
listRepository: {
query: ({ watch, ...queryArg }) => ({
url: `/repositories`,
params: queryArg,
}),
onCacheEntryAdded: createOnCacheEntryAdded<RepositorySpec, RepositoryStatus, Repository, RepositoryList>(
'repositories'
),
onCacheEntryAdded: createOnCacheEntryAdded<RepositorySpec, RepositoryStatus>('repositories'),
},
deleteRepository: {
onQueryStarted: async (_, { queryFulfilled, dispatch }) => {
@@ -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<Spec, Status>(resourceName: string) {
return async function onCacheEntryAdded<List extends ResourceList<Spec, Status>>(
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;
}
});
});
+26
View File
@@ -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<T = object, S = object, K = string>(
generated: GeneratedResource<T, S, K>
): generated is Resource<T, S, K> {
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<T = object, S = object, K = string>(
generatedList: GeneratedResourceList<T, S, K>
): generatedList is ResourceList<T, S, K> {
return !!generatedList.metadata?.resourceVersion && Array.isArray(generatedList.items);
}
+15
View File
@@ -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<ObjectMeta> {}
export interface GeneratedResource<T = object, S = object, K = string> extends Partial<TypeMeta<K>> {
metadata?: GeneratedObjectMeta;
spec?: T;
status?: S;
}
export interface GeneratedResourceList<Spec, Status, K = string> {
metadata?: Partial<ListMeta>;
items?: Array<GeneratedResource<Spec, Status, K>>;
}