From 2a617abd77530760b4b66daf6fc1390ed74429b3 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Tue, 19 Aug 2025 16:01:38 +0300 Subject: [PATCH] Api clients: Update provisioning, playlist clients and fix type errors (#109850) * Update provisioning types * Update playlist types * Revert * import * lint --- .../playlist/v0alpha1/endpoints.gen.ts | 63 +++++++++---------- .../api/clients/playlist/v0alpha1/index.ts | 27 ++++---- .../provisioning/v0alpha1/endpoints.gen.ts | 5 +- public/app/features/playlist/PlaylistCard.tsx | 6 +- .../features/playlist/PlaylistEditPage.tsx | 2 +- public/app/features/playlist/PlaylistForm.tsx | 8 ++- public/app/features/playlist/PlaylistPage.tsx | 6 +- .../features/playlist/PlaylistPageList.tsx | 2 +- public/app/features/playlist/PlaylistSrv.ts | 6 +- public/app/features/playlist/StartModal.tsx | 4 +- public/app/features/playlist/types.ts | 4 +- public/app/features/playlist/utils.ts | 2 +- public/app/features/provisioning/types.ts | 1 + .../features/provisioning/utils/repository.ts | 8 ++- 14 files changed, 75 insertions(+), 69 deletions(-) diff --git a/public/app/api/clients/playlist/v0alpha1/endpoints.gen.ts b/public/app/api/clients/playlist/v0alpha1/endpoints.gen.ts index 7c5cd879b1d..81c1a4e1204 100644 --- a/public/app/api/clients/playlist/v0alpha1/endpoints.gen.ts +++ b/public/app/api/clients/playlist/v0alpha1/endpoints.gen.ts @@ -261,43 +261,43 @@ export type ObjectMeta = { Populated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ uid?: string; }; -export type PlaylistItem = { - /** type of the item. */ - type: string; - /** Value depends on type and describes the playlist item. - - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This - is not portable as the numerical identifier is non-deterministic between different instances. - Will be replaced by dashboard_by_uid in the future. (deprecated) - - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All - dashboards behind the tag will be added to the playlist. - - dashboard_by_uid: The value is the dashboard UID */ - value: string; -}; export type PlaylistSpec = { interval: string; - items: PlaylistItem[]; + items: { + /** type of the item. */ + type: 'dashboard_by_tag' | 'dashboard_by_uid' | 'dashboard_by_id'; + /** Value depends on type and describes the playlist item. + - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This + is not portable as the numerical identifier is non-deterministic between different instances. + Will be replaced by dashboard_by_uid in the future. (deprecated) + - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All + dashboards behind the tag will be added to the playlist. + - dashboard_by_uid: The value is the dashboard UID */ + value: string; + }[]; title: string; }; -export type PlayliststatusOperatorState = { - /** descriptiveState is an optional more descriptive state field which has no requirements on format */ - descriptiveState?: string; - /** details contains any extra information that is operator-specific */ - details?: { - [key: string]: object; - }; - /** lastEvaluation is the ResourceVersion last evaluated */ - lastEvaluation: string; - /** state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation. */ - state: string; -}; export type PlaylistStatus = { /** additionalFields is reserved for future use */ additionalFields?: { - [key: string]: object; + [key: string]: any; }; - /** operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field. */ + /** operatorStates is a map of operator ID to operator state evaluations. + Any operator which consumes this kind SHOULD add its state evaluation information to this field. */ operatorStates?: { - [key: string]: PlayliststatusOperatorState; + [key: string]: { + /** descriptiveState is an optional more descriptive state field which has no requirements on format */ + descriptiveState?: string; + /** details contains any extra information that is operator-specific */ + details?: { + [key: string]: any; + }; + /** lastEvaluation is the ResourceVersion last evaluated */ + lastEvaluation: string; + /** state describes the state of the lastEvaluation. + It is limited to three possible states for machine evaluation. */ + state: 'success' | 'in_progress' | 'failed'; + }; }; }; export type Playlist = { @@ -305,10 +305,9 @@ export type Playlist = { apiVersion?: string; /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ kind?: string; - metadata: ObjectMeta; - /** Spec is the spec of the Playlist */ - spec: PlaylistSpec; - status: PlaylistStatus; + metadata?: ObjectMeta; + spec?: PlaylistSpec; + status?: PlaylistStatus; }; export type ListMeta = { /** continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message. */ diff --git a/public/app/api/clients/playlist/v0alpha1/index.ts b/public/app/api/clients/playlist/v0alpha1/index.ts index f42d9a318f2..3beec79634b 100644 --- a/public/app/api/clients/playlist/v0alpha1/index.ts +++ b/public/app/api/clients/playlist/v0alpha1/index.ts @@ -17,17 +17,20 @@ export const playlistAPIv0alpha1 = generatedAPI.enhanceEndpoints({ }, createPlaylist: (endpointDefinition) => { const originalQuery = endpointDefinition.query; - if (originalQuery) { - endpointDefinition.query = (requestOptions) => { - if (!requestOptions.playlist.metadata.name && !requestOptions.playlist.metadata.generateName) { - const login = contextSrv.user.login; - // GenerateName lets the apiserver create a new uid for the name - // The passed in value is the suggested prefix - requestOptions.playlist.metadata.generateName = login ? login.slice(0, 2) : 'g'; - } - return originalQuery(requestOptions); - }; + if (!originalQuery) { + return; } + + endpointDefinition.query = (requestOptions) => { + const metadata = requestOptions.playlist.metadata; + if (metadata && !metadata.name && !metadata.generateName) { + // GenerateName lets the apiserver create a new uid for the name + // The passed in value is the suggested prefix + metadata.generateName = contextSrv.user.login?.slice(0, 2) || 'g'; + } + return originalQuery(requestOptions); + }; + endpointDefinition.onQueryStarted = async (_, { queryFulfilled, dispatch }) => { try { await queryFulfilled; @@ -61,7 +64,7 @@ export const playlistAPIv0alpha1 = generatedAPI.enhanceEndpoints({ }); /** @deprecated -- this migrates playlists saved with internal ids to uid */ -async function migrateInternalIDs(playlist: PlaylistSpec) { +async function migrateInternalIDs(playlist?: PlaylistSpec) { if (playlist?.items) { for (const item of playlist.items) { if (item.type === 'dashboard_by_id') { @@ -84,4 +87,4 @@ export const { } = playlistAPIv0alpha1; // eslint-disable-next-line no-barrel-files/no-barrel-files -export type { Playlist } from './endpoints.gen'; +export type { Playlist, PlaylistSpec } from './endpoints.gen'; diff --git a/public/app/api/clients/provisioning/v0alpha1/endpoints.gen.ts b/public/app/api/clients/provisioning/v0alpha1/endpoints.gen.ts index bc262a42bae..33ee7549eb7 100644 --- a/public/app/api/clients/provisioning/v0alpha1/endpoints.gen.ts +++ b/public/app/api/clients/provisioning/v0alpha1/endpoints.gen.ts @@ -1013,7 +1013,7 @@ export type RepositorySpec = { - `"local"` */ type: 'bitbucket' | 'git' | 'github' | 'gitlab' | 'local'; /** UI driven Workflow that allow changes to the contends of the repository. The order is relevant for defining the precedence of the workflows. When empty, the repository does not support any edits (eg, readonly) */ - workflows: RepoWorkflows; + workflows: ('branch' | 'write')[]; }; export type HealthStatus = { /** When the health was checked last time */ @@ -1258,7 +1258,6 @@ export type WebhookResponse = { /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ kind?: string; }; -export type RepoWorkflows = ('branch' | 'write')[] export type RepositoryView = { /** For git, this is the target branch */ branch?: string; @@ -1282,7 +1281,7 @@ export type RepositoryView = { - `"local"` */ type: 'bitbucket' | 'git' | 'github' | 'gitlab' | 'local'; /** The supported workflows */ - workflows: RepoWorkflows; + workflows: ('branch' | 'write')[]; }; export type RepositoryViewList = { /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ diff --git a/public/app/features/playlist/PlaylistCard.tsx b/public/app/features/playlist/PlaylistCard.tsx index 458e5031aca..b0bfb548c81 100644 --- a/public/app/features/playlist/PlaylistCard.tsx +++ b/public/app/features/playlist/PlaylistCard.tsx @@ -22,7 +22,7 @@ const PlaylistCardComponent = ({ playlist, setStartPlaylist, setPlaylistToDelete return ( - {playlist.spec.title} + {playlist.spec?.title} {({ showModal, hideModal }) => ( { showModal(ShareModal, { - playlistUid: playlist.metadata.name ?? '', + playlistUid: playlist.metadata?.name ?? '', onDismiss: hideModal, }); }} @@ -45,7 +45,7 @@ const PlaylistCardComponent = ({ playlist, setStartPlaylist, setPlaylistToDelete {contextSrv.isEditor && ( <> - + Edit playlist diff --git a/public/app/features/playlist/types.ts b/public/app/features/playlist/types.ts index e6e05ed8ca6..da174f5458c 100644 --- a/public/app/features/playlist/types.ts +++ b/public/app/features/playlist/types.ts @@ -1,8 +1,8 @@ -import { Playlist } from '../../api/clients/playlist/v0alpha1'; +import { PlaylistSpec } from '../../api/clients/playlist/v0alpha1'; import { DashboardQueryResult } from '../search/service/types'; export type PlaylistMode = boolean; -type PlaylistItem = Playlist['spec']['items'][number]; +type PlaylistItem = PlaylistSpec['items'][number]; export interface PlaylistItemUI extends PlaylistItem { /** diff --git a/public/app/features/playlist/utils.ts b/public/app/features/playlist/utils.ts index 78f7206ba2f..d9c9635ee70 100644 --- a/public/app/features/playlist/utils.ts +++ b/public/app/features/playlist/utils.ts @@ -88,5 +88,5 @@ export function searchPlaylists(playlists: Playlist[], query?: string): Playlist return playlists; } query = query.toLowerCase(); - return playlists.filter((v) => v.spec.title.toLowerCase().includes(query!)); + return playlists.filter((v) => v.spec?.title.toLowerCase().includes(query!)); } diff --git a/public/app/features/provisioning/types.ts b/public/app/features/provisioning/types.ts index d008b17f5bc..c1f02e9be1e 100644 --- a/public/app/features/provisioning/types.ts +++ b/public/app/features/provisioning/types.ts @@ -14,6 +14,7 @@ import { // Repository type definition - extracted from API client export type RepositoryType = RepositorySpec['type']; +export type RepoWorkflows = RepositorySpec['workflows']; // Field configuration interface export interface RepositoryFieldData { diff --git a/public/app/features/provisioning/utils/repository.ts b/public/app/features/provisioning/utils/repository.ts index c30d026196c..eedac587589 100644 --- a/public/app/features/provisioning/utils/repository.ts +++ b/public/app/features/provisioning/utils/repository.ts @@ -1,8 +1,10 @@ import { t } from '@grafana/i18n'; -import { RepositoryView, RepoWorkflows } from 'app/api/clients/provisioning/v0alpha1'; +import { RepositoryView } from 'app/api/clients/provisioning/v0alpha1'; + +import { RepoWorkflows } from '../types'; export function getIsReadOnlyWorkflows(workflows?: RepoWorkflows): boolean { - // Repository is consider read-only if it has no workflows defined (workflows are required for write operations) + // Repository is considered read-only if it has no workflows defined (workflows are required for write operations) return workflows?.length === 0; } @@ -14,7 +16,7 @@ export function getIsReadOnlyRepo(repository: RepositoryView | undefined): boole return getIsReadOnlyWorkflows(repository.workflows); } -// Right now we only support local file provisioning message and git provisioned. This can be extend in the future as needed. +// Right now we only support local file provisioning message and git provisioned. This can be extended in the future as needed. export const getReadOnlyTooltipText = ({ isLocal = false }) => { return isLocal ? t(