From 664ebf771e2a1d2f94bc14d3dababb634c4ac40f Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Thu, 5 Oct 2023 15:00:36 -0400 Subject: [PATCH] K8s: Playlist frontend reads (#76057) --- .../feature-toggles/index.md | 1 + .../src/types/featureToggles.gen.ts | 1 + pkg/apis/playlist/v0alpha1/legacy_storage.go | 14 ++++++- pkg/apis/playlist/v0alpha1/types.go | 4 +- pkg/services/featuremgmt/registry.go | 7 ++++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 ++ pkg/services/grafana-apiserver/README.md | 1 + .../features/playlist/PlaylistEditPage.tsx | 4 +- public/app/features/playlist/PlaylistPage.tsx | 4 +- .../app/features/playlist/PlaylistSrv.test.ts | 18 +++++---- public/app/features/playlist/PlaylistSrv.ts | 4 +- public/app/features/playlist/api.ts | 39 +++++++++++++++++-- public/app/features/playlist/types.ts | 13 +++++++ 14 files changed, 98 insertions(+), 17 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index e3b43596eaf..17ebb0d39ea 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -144,6 +144,7 @@ Experimental features might be changed or removed without prior notice. | `alertingModifiedExport` | Enables using UI for provisioned rules modification and export | | `enableNativeHTTPHistogram` | Enables native HTTP Histograms | | `transformationsVariableSupport` | Allows using variables in transformations | +| `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists | ## Development feature toggles diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index f9fc9655152..d779bb753f3 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -137,4 +137,5 @@ export interface FeatureToggles { alertingModifiedExport?: boolean; enableNativeHTTPHistogram?: boolean; transformationsVariableSupport?: boolean; + kubernetesPlaylists?: boolean; } diff --git a/pkg/apis/playlist/v0alpha1/legacy_storage.go b/pkg/apis/playlist/v0alpha1/legacy_storage.go index 15e4d1cdc50..1880499ce4f 100644 --- a/pkg/apis/playlist/v0alpha1/legacy_storage.go +++ b/pkg/apis/playlist/v0alpha1/legacy_storage.go @@ -9,6 +9,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" + playlistkind "github.com/grafana/grafana/pkg/kinds/playlist" grafanarequest "github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/playlist" ) @@ -88,8 +89,12 @@ func (s *legacyStorage) List(ctx context.Context, options *internalversion.ListO ObjectMeta: metav1.ObjectMeta{ Name: v.UID, }, + Spec: playlistkind.Spec{ + Name: v.Name, + Uid: v.UID, + Interval: v.Interval, + }, } - p.Name = v.Name + " // " + v.Interval list.Items = append(list.Items, p) } if len(list.Items) == limit { @@ -123,6 +128,11 @@ func (s *legacyStorage) Get(ctx context.Context, name string, options *metav1.Ge ObjectMeta: metav1.ObjectMeta{ Name: p.Uid, }, - Name: p.Name + "//" + p.Interval, + Spec: playlistkind.Spec{ + Name: p.Name, + Uid: p.Uid, + Interval: p.Interval, + Items: p.Items, + }, }, nil } diff --git a/pkg/apis/playlist/v0alpha1/types.go b/pkg/apis/playlist/v0alpha1/types.go index 62082161720..2e94bd84f44 100644 --- a/pkg/apis/playlist/v0alpha1/types.go +++ b/pkg/apis/playlist/v0alpha1/types.go @@ -2,6 +2,8 @@ package v0alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/grafana/grafana/pkg/kinds/playlist" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -12,7 +14,7 @@ type Playlist struct { // +optional metav1.ObjectMeta `json:"metadata,omitempty"` - Name string `json:"name,omitempty"` + Spec playlist.Spec `json:"spec,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index ca6130d155b..eac13ab729b 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -831,5 +831,12 @@ var ( Stage: FeatureStageExperimental, Owner: grafanaBiSquad, }, + { + Name: "kubernetesPlaylists", + Description: "Use the kubernetes API in the frontend for playlists", + FrontendOnly: true, + Stage: FeatureStageExperimental, + Owner: grafanaAppPlatformSquad, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 04aab1b8a94..0f02d3fb772 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -118,3 +118,4 @@ externalServiceAccounts,experimental,@grafana/grafana-authnz-team,true,false,fal alertingModifiedExport,experimental,@grafana/alerting-squad,false,false,false,false enableNativeHTTPHistogram,experimental,@grafana/hosted-grafana-team,false,false,false,false transformationsVariableSupport,experimental,@grafana/grafana-bi-squad,false,false,false,true +kubernetesPlaylists,experimental,@grafana/grafana-app-platform-squad,false,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 1c5a647d2cc..f8db90709e8 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -482,4 +482,8 @@ const ( // FlagTransformationsVariableSupport // Allows using variables in transformations FlagTransformationsVariableSupport = "transformationsVariableSupport" + + // FlagKubernetesPlaylists + // Use the kubernetes API in the frontend for playlists + FlagKubernetesPlaylists = "kubernetesPlaylists" ) diff --git a/pkg/services/grafana-apiserver/README.md b/pkg/services/grafana-apiserver/README.md index 4c68a3a2ce6..2b882b0513f 100644 --- a/pkg/services/grafana-apiserver/README.md +++ b/pkg/services/grafana-apiserver/README.md @@ -7,6 +7,7 @@ app_mode = development [feature_toggles] grafanaAPIServer = true +kubernetesPlaylists = true ``` Start Grafana: diff --git a/public/app/features/playlist/PlaylistEditPage.tsx b/public/app/features/playlist/PlaylistEditPage.tsx index d827db88755..e5d26cb12cf 100644 --- a/public/app/features/playlist/PlaylistEditPage.tsx +++ b/public/app/features/playlist/PlaylistEditPage.tsx @@ -8,9 +8,11 @@ import { t, Trans } from 'app/core/internationalization'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { PlaylistForm } from './PlaylistForm'; -import { getPlaylist, updatePlaylist } from './api'; +import { playlistAPI, updatePlaylist } from './api'; import { Playlist } from './types'; +const { getPlaylist } = playlistAPI; + export interface RouteParams { uid: string; } diff --git a/public/app/features/playlist/PlaylistPage.tsx b/public/app/features/playlist/PlaylistPage.tsx index f556fafd000..8a76495fb32 100644 --- a/public/app/features/playlist/PlaylistPage.tsx +++ b/public/app/features/playlist/PlaylistPage.tsx @@ -11,9 +11,11 @@ import { contextSrv } from 'app/core/services/context_srv'; import { EmptyQueryListBanner } from './EmptyQueryListBanner'; import { PlaylistPageList } from './PlaylistPageList'; import { StartModal } from './StartModal'; -import { deletePlaylist, getAllPlaylist, searchPlaylists } from './api'; +import { deletePlaylist, searchPlaylists, playlistAPI } from './api'; import { Playlist } from './types'; +const { getAllPlaylist } = playlistAPI; + export const PlaylistPage = () => { const [forcePlaylistsFetch, setForcePlaylistsFetch] = useState(0); const [searchQuery, setSearchQuery] = useState(''); diff --git a/public/app/features/playlist/PlaylistSrv.test.ts b/public/app/features/playlist/PlaylistSrv.test.ts index ba88b3551a5..0c4910caae0 100644 --- a/public/app/features/playlist/PlaylistSrv.test.ts +++ b/public/app/features/playlist/PlaylistSrv.test.ts @@ -11,14 +11,16 @@ import { PlaylistSrv } from './PlaylistSrv'; import { Playlist, PlaylistItem } from './types'; jest.mock('./api', () => ({ - getPlaylist: jest.fn().mockReturnValue({ - interval: '1s', - uid: 'xyz', - items: [ - { type: 'dashboard_by_uid', value: 'aaa' }, - { type: 'dashboard_by_uid', value: 'bbb' }, - ], - } as Playlist), + playlistAPI: { + getPlaylist: jest.fn().mockReturnValue({ + interval: '1s', + uid: 'xyz', + items: [ + { type: 'dashboard_by_uid', value: 'aaa' }, + { type: 'dashboard_by_uid', value: 'bbb' }, + ], + } as Playlist), + }, loadDashboards: (items: PlaylistItem[]) => { return Promise.resolve( items.map((v) => ({ diff --git a/public/app/features/playlist/PlaylistSrv.ts b/public/app/features/playlist/PlaylistSrv.ts index 71de3436099..409469c9e5a 100644 --- a/public/app/features/playlist/PlaylistSrv.ts +++ b/public/app/features/playlist/PlaylistSrv.ts @@ -4,7 +4,9 @@ import { pickBy } from 'lodash'; import { locationUtil, urlUtil, rangeUtil } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { getPlaylist, loadDashboards } from './api'; +import { playlistAPI, loadDashboards } from './api'; + +const { getPlaylist } = playlistAPI; export const queryParamsToPreserve: { [key: string]: boolean } = { kiosk: true, diff --git a/public/app/features/playlist/api.ts b/public/app/features/playlist/api.ts index b1d833508d2..85b76ab99c4 100644 --- a/public/app/features/playlist/api.ts +++ b/public/app/features/playlist/api.ts @@ -4,13 +4,14 @@ import { DataQueryRequest, DataFrameView } from '@grafana/data'; import { getBackendSrv, config } from '@grafana/runtime'; import { notifyApp } from 'app/core/actions'; import { createErrorNotification, createSuccessNotification } from 'app/core/copy/appNotification'; +import { contextSrv } from 'app/core/services/context_srv'; import { getGrafanaDatasource } from 'app/plugins/datasource/grafana/datasource'; import { GrafanaQuery, GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; import { dispatch } from 'app/store/store'; import { DashboardQueryResult, getGrafanaSearcher, SearchQuery } from '../search/service'; -import { Playlist, PlaylistItem } from './types'; +import { Playlist, PlaylistItem, KubernetesPlaylist, KubernetesPlaylistList, PlaylistAPI } from './types'; export async function createPlaylist(playlist: Playlist) { await withErrorHandling(() => getBackendSrv().post('/api/playlists', playlist)); @@ -24,8 +25,40 @@ export async function deletePlaylist(uid: string) { await withErrorHandling(() => getBackendSrv().delete(`/api/playlists/${uid}`), 'Playlist deleted'); } +export const playlistAPI: PlaylistAPI = { + getPlaylist: config.featureToggles.kubernetesPlaylists ? k8sGetPlaylist : legacyGetPlaylist, + getAllPlaylist: config.featureToggles.kubernetesPlaylists ? k8sGetAllPlaylist : legacyGetAllPlaylist, +}; + /** This returns a playlist where all ids are replaced with UIDs */ -export async function getPlaylist(uid: string): Promise { +export async function k8sGetPlaylist(uid: string): Promise { + const k8splaylist = await getBackendSrv().get( + `/apis/playlist.x.grafana.com/v0alpha1/namespaces/org-${contextSrv.user.orgId}/playlists/${uid}` + ); + const playlist = k8splaylist.spec; + if (playlist.items) { + for (const item of playlist.items) { + if (item.type === 'dashboard_by_id') { + item.type = 'dashboard_by_uid'; + const uids = await getBackendSrv().get(`/api/dashboards/ids/${item.value}`); + if (uids.length) { + item.value = uids[0]; + } + } + } + } + return playlist; +} + +export async function k8sGetAllPlaylist(): Promise { + const k8splaylists = await getBackendSrv().get( + `/apis/playlist.x.grafana.com/v0alpha1/namespaces/org-${contextSrv.user.orgId}/playlists` + ); + return k8splaylists.playlists.map((p) => p.spec); +} + +/** This returns a playlist where all ids are replaced with UIDs */ +export async function legacyGetPlaylist(uid: string): Promise { const playlist = await getBackendSrv().get(`/api/playlists/${uid}`); if (playlist.items) { for (const item of playlist.items) { @@ -41,7 +74,7 @@ export async function getPlaylist(uid: string): Promise { return playlist; } -export async function getAllPlaylist(): Promise { +export async function legacyGetAllPlaylist(): Promise { return getBackendSrv().get('/api/playlists/'); } diff --git a/public/app/features/playlist/types.ts b/public/app/features/playlist/types.ts index 65b291c3515..19cf25f5fbf 100644 --- a/public/app/features/playlist/types.ts +++ b/public/app/features/playlist/types.ts @@ -11,6 +11,19 @@ export interface PlayListItemDTO { type: 'dashboard' | 'tag'; } +export interface PlaylistAPI { + getPlaylist(uid: string): Promise; + getAllPlaylist(): Promise; +} + +export interface KubernetesPlaylistList { + playlists: KubernetesPlaylist[]; +} + +export interface KubernetesPlaylist { + spec: Playlist; +} + export interface Playlist { uid: string; name: string;