K8s: Playlist frontend reads (#76057)

This commit is contained in:
Todd Treece 2023-10-05 15:00:36 -04:00 committed by GitHub
parent 726260b2f6
commit 664ebf771e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 98 additions and 17 deletions

View File

@ -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

View File

@ -137,4 +137,5 @@ export interface FeatureToggles {
alertingModifiedExport?: boolean;
enableNativeHTTPHistogram?: boolean;
transformationsVariableSupport?: boolean;
kubernetesPlaylists?: boolean;
}

View File

@ -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
}

View File

@ -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

View File

@ -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,
},
}
)

View File

@ -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

1 Name Stage Owner requiresDevMode RequiresLicense RequiresRestart FrontendOnly
118 alertingModifiedExport experimental @grafana/alerting-squad false false false false
119 enableNativeHTTPHistogram experimental @grafana/hosted-grafana-team false false false false
120 transformationsVariableSupport experimental @grafana/grafana-bi-squad false false false true
121 kubernetesPlaylists experimental @grafana/grafana-app-platform-squad false false false true

View File

@ -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"
)

View File

@ -7,6 +7,7 @@ app_mode = development
[feature_toggles]
grafanaAPIServer = true
kubernetesPlaylists = true
```
Start Grafana:

View File

@ -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;
}

View File

@ -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('');

View File

@ -11,6 +11,7 @@ import { PlaylistSrv } from './PlaylistSrv';
import { Playlist, PlaylistItem } from './types';
jest.mock('./api', () => ({
playlistAPI: {
getPlaylist: jest.fn().mockReturnValue({
interval: '1s',
uid: 'xyz',
@ -19,6 +20,7 @@ jest.mock('./api', () => ({
{ type: 'dashboard_by_uid', value: 'bbb' },
],
} as Playlist),
},
loadDashboards: (items: PlaylistItem[]) => {
return Promise.resolve(
items.map((v) => ({

View File

@ -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,

View File

@ -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<Playlist> {
export async function k8sGetPlaylist(uid: string): Promise<Playlist> {
const k8splaylist = await getBackendSrv().get<KubernetesPlaylist>(
`/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<string[]>(`/api/dashboards/ids/${item.value}`);
if (uids.length) {
item.value = uids[0];
}
}
}
}
return playlist;
}
export async function k8sGetAllPlaylist(): Promise<Playlist[]> {
const k8splaylists = await getBackendSrv().get<KubernetesPlaylistList>(
`/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<Playlist> {
const playlist = await getBackendSrv().get<Playlist>(`/api/playlists/${uid}`);
if (playlist.items) {
for (const item of playlist.items) {
@ -41,7 +74,7 @@ export async function getPlaylist(uid: string): Promise<Playlist> {
return playlist;
}
export async function getAllPlaylist(): Promise<Playlist[]> {
export async function legacyGetAllPlaylist(): Promise<Playlist[]> {
return getBackendSrv().get<Playlist[]>('/api/playlists/');
}

View File

@ -11,6 +11,19 @@ export interface PlayListItemDTO {
type: 'dashboard' | 'tag';
}
export interface PlaylistAPI {
getPlaylist(uid: string): Promise<Playlist>;
getAllPlaylist(): Promise<Playlist[]>;
}
export interface KubernetesPlaylistList {
playlists: KubernetesPlaylist[];
}
export interface KubernetesPlaylist {
spec: Playlist;
}
export interface Playlist {
uid: string;
name: string;