Skip to content

Commit

Permalink
Playlist: Add an api wrapper for playlist requests (#76308)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryantxu committed Oct 11, 2023
1 parent bf7fae4 commit 6983af3
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 82 deletions.
1 change: 1 addition & 0 deletions public/app/features/playlist/PlaylistEditPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ describe('PlaylistEditPage', () => {
fireEvent.submit(screen.getByRole('button', { name: /save/i }));
await waitFor(() => expect(putMock).toHaveBeenCalledTimes(1));
expect(putMock).toHaveBeenCalledWith('/api/playlists/foo', {
uid: 'foo',
name: 'A Name',
interval: '10s',
items: [{ title: 'First item', type: 'dashboard_by_uid', order: 1, value: '1' }],
Expand Down
9 changes: 4 additions & 5 deletions public/app/features/playlist/PlaylistEditPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,21 @@ import { t, Trans } from 'app/core/internationalization';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';

import { PlaylistForm } from './PlaylistForm';
import { playlistAPI, updatePlaylist } from './api';
import { getPlaylistAPI } from './api';
import { Playlist } from './types';

const { getPlaylist } = playlistAPI;

export interface RouteParams {
uid: string;
}

interface Props extends GrafanaRouteComponentProps<RouteParams> {}

export const PlaylistEditPage = ({ match }: Props) => {
const playlist = useAsync(() => getPlaylist(match.params.uid), [match.params]);
const api = getPlaylistAPI();
const playlist = useAsync(() => api.getPlaylist(match.params.uid), [match.params]);

const onSubmit = async (playlist: Playlist) => {
await updatePlaylist(match.params.uid, playlist);
await api.updatePlaylist(playlist);
locationService.push('/playlists');
};

Expand Down
1 change: 1 addition & 0 deletions public/app/features/playlist/PlaylistForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ describe('PlaylistForm', () => {
await userEvent.click(screen.getByRole('button', { name: /save/i }));
expect(onSubmitMock).toHaveBeenCalledTimes(1);
expect(onSubmitMock).toHaveBeenCalledWith({
uid: 'foo',
name: 'A test playlist',
interval: '10m',
items: [
Expand Down
2 changes: 1 addition & 1 deletion public/app/features/playlist/PlaylistForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const PlaylistForm = ({ onSubmit, playlist }: Props) => {

const doSubmit = (list: Playlist) => {
setSaving(true);
onSubmit({ ...list, items });
onSubmit({ ...list, items, uid: playlist.uid });
};

return (
Expand Down
1 change: 1 addition & 0 deletions public/app/features/playlist/PlaylistNewPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ describe('PlaylistNewPage', () => {
await waitFor(() => expect(backendSrvMock).toHaveBeenCalledTimes(1));
expect(backendSrvMock).toHaveBeenCalledWith('/api/playlists', {
name: 'A new name',
uid: '',
interval: '5m',
items: [],
});
Expand Down
4 changes: 2 additions & 2 deletions public/app/features/playlist/PlaylistNewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { locationService } from '@grafana/runtime';
import { Page } from 'app/core/components/Page/Page';

import { PlaylistForm } from './PlaylistForm';
import { createPlaylist, getDefaultPlaylist } from './api';
import { getPlaylistAPI, getDefaultPlaylist } from './api';
import { Playlist } from './types';

export const PlaylistNewPage = () => {
const [playlist] = useState<Playlist>(getDefaultPlaylist());

const onSubmit = async (playlist: Playlist) => {
await createPlaylist(playlist);
await getPlaylistAPI().createPlaylist(playlist);
locationService.push('/playlists');
};

Expand Down
9 changes: 4 additions & 5 deletions public/app/features/playlist/PlaylistPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ import { contextSrv } from 'app/core/services/context_srv';
import { EmptyQueryListBanner } from './EmptyQueryListBanner';
import { PlaylistPageList } from './PlaylistPageList';
import { StartModal } from './StartModal';
import { deletePlaylist, searchPlaylists, playlistAPI } from './api';
import { getPlaylistAPI, searchPlaylists } from './api';
import { Playlist } from './types';

const { getAllPlaylist } = playlistAPI;

export const PlaylistPage = () => {
const api = getPlaylistAPI();
const [forcePlaylistsFetch, setForcePlaylistsFetch] = useState(0);
const [searchQuery, setSearchQuery] = useState('');
const allPlaylists = useAsync(() => getAllPlaylist(), [forcePlaylistsFetch]);
const allPlaylists = useAsync(() => api.getAllPlaylist(), [forcePlaylistsFetch]);
const playlists = useMemo(() => searchPlaylists(allPlaylists.value ?? [], searchQuery), [searchQuery, allPlaylists]);

const [startPlaylist, setStartPlaylist] = useState<Playlist | undefined>();
Expand All @@ -31,7 +30,7 @@ export const PlaylistPage = () => {
if (!playlistToDelete) {
return;
}
deletePlaylist(playlistToDelete.uid).finally(() => {
api.deletePlaylist(playlistToDelete.uid).finally(() => {
setForcePlaylistsFetch(forcePlaylistsFetch + 1);
setPlaylistToDelete(undefined);
});
Expand Down
5 changes: 3 additions & 2 deletions public/app/features/playlist/PlaylistSrv.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ import { PlaylistSrv } from './PlaylistSrv';
import { Playlist, PlaylistItem } from './types';

jest.mock('./api', () => ({
playlistAPI: {
getPlaylistAPI: () => ({
getPlaylist: jest.fn().mockReturnValue({
interval: '1s',
uid: 'xyz',
name: 'The display',
items: [
{ type: 'dashboard_by_uid', value: 'aaa' },
{ type: 'dashboard_by_uid', value: 'bbb' },
],
} as Playlist),
},
}),
loadDashboards: (items: PlaylistItem[]) => {
return Promise.resolve(
items.map((v) => ({
Expand Down
9 changes: 5 additions & 4 deletions public/app/features/playlist/PlaylistSrv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import { pickBy } from 'lodash';
import { locationUtil, urlUtil, rangeUtil } from '@grafana/data';
import { locationService } from '@grafana/runtime';

import { playlistAPI, loadDashboards } from './api';

const { getPlaylist } = playlistAPI;
import { getPlaylistAPI, loadDashboards } from './api';
import { PlaylistAPI } from './types';

export const queryParamsToPreserve: { [key: string]: boolean } = {
kiosk: true,
Expand All @@ -23,11 +22,13 @@ export class PlaylistSrv {
private numberOfLoops = 0;
private declare validPlaylistUrl: string;
private locationListenerUnsub?: () => void;
private api: PlaylistAPI;

isPlaying = false;

constructor() {
this.locationUpdated = this.locationUpdated.bind(this);
this.api = getPlaylistAPI();
}

next() {
Expand Down Expand Up @@ -81,7 +82,7 @@ export class PlaylistSrv {
this.locationListenerUnsub = locationService.getHistory().listen(this.locationUpdated);

const urls: string[] = [];
let playlist = await getPlaylist(playlistUid);
let playlist = await this.api.getPlaylist(playlistUid);
if (!playlist.items?.length) {
// alert
return;
Expand Down
151 changes: 108 additions & 43 deletions public/app/features/playlist/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,71 +11,132 @@ import { dispatch } from 'app/store/store';

import { DashboardQueryResult, getGrafanaSearcher, SearchQuery } from '../search/service';

import { Playlist, PlaylistItem, KubernetesPlaylist, KubernetesPlaylistList, PlaylistAPI } from './types';
import { Playlist, PlaylistItem, PlaylistAPI } from './types';

export async function createPlaylist(playlist: Playlist) {
await withErrorHandling(() => getBackendSrv().post('/api/playlists', playlist));
class LegacyAPI implements PlaylistAPI {
async getAllPlaylist(): Promise<Playlist[]> {
return getBackendSrv().get<Playlist[]>('/api/playlists/');
}

async getPlaylist(uid: string): Promise<Playlist> {
const p = await getBackendSrv().get<Playlist>(`/api/playlists/${uid}`);
await migrateInternalIDs(p);
return p;
}

async createPlaylist(playlist: Playlist): Promise<void> {
await withErrorHandling(() => getBackendSrv().post('/api/playlists', playlist));
}

async updatePlaylist(playlist: Playlist): Promise<void> {
await withErrorHandling(() => getBackendSrv().put(`/api/playlists/${playlist.uid}`, playlist));
}

async deletePlaylist(uid: string): Promise<void> {
await withErrorHandling(() => getBackendSrv().delete(`/api/playlists/${uid}`), 'Playlist deleted');
}
}

export async function updatePlaylist(uid: string, playlist: Playlist) {
await withErrorHandling(() => getBackendSrv().put(`/api/playlists/${uid}`, playlist));
interface K8sPlaylistList {
playlists: K8sPlaylist[];
}

export async function deletePlaylist(uid: string) {
await withErrorHandling(() => getBackendSrv().delete(`/api/playlists/${uid}`), 'Playlist deleted');
interface K8sPlaylist {
metadata: {
name: string;
};
spec: {
name: string;
interval: string;
items: PlaylistItem[];
};
}

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 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];
}
}
class K8sAPI implements PlaylistAPI {
readonly url = `/apis/playlist.x.grafana.com/v0alpha1/namespaces/org-${contextSrv.user.orgId}/playlists`;
readonly legacy = new LegacyAPI(); // set to null for full CRUD

async getAllPlaylist(): Promise<Playlist[]> {
const result = await getBackendSrv().get<K8sPlaylistList>(this.url);
console.log('getAllPlaylist', result);
const v = result.playlists.map(k8sResourceAsPlaylist);
console.log('after', v);
return v;
}

async getPlaylist(uid: string): Promise<Playlist> {
const r = await getBackendSrv().get<K8sPlaylist>(this.url + '/' + uid);
const p = k8sResourceAsPlaylist(r);
await migrateInternalIDs(p);
return p;
}

async createPlaylist(playlist: Playlist): Promise<void> {
if (this.legacy) {
return this.legacy.createPlaylist(playlist);
}
await withErrorHandling(() =>
getBackendSrv().post(this.url, {
apiVersion: 'playlists.grafana.com/v0alpha1',
kind: 'Playlist',
metadata: {
name: playlist.uid,
},
spec: playlist,
})
);
}

async updatePlaylist(playlist: Playlist): Promise<void> {
if (this.legacy) {
return this.legacy.updatePlaylist(playlist);
}
await withErrorHandling(() =>
getBackendSrv().put(`${this.url}/${playlist.uid}`, {
apiVersion: 'playlists.grafana.com/v0alpha1',
kind: 'Playlist',
metadata: {
name: playlist.uid,
},
spec: {
...playlist,
title: playlist.name,
},
})
);
}

async deletePlaylist(uid: string): Promise<void> {
if (this.legacy) {
return this.legacy.deletePlaylist(uid);
}
await withErrorHandling(() => getBackendSrv().delete(`${this.url}/${uid}`), 'Playlist deleted');
}
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 converts a saved k8s resource into a playlist object
// the main difference is that k8s uses metdata.name as the uid
// to avoid future confusion, the display name is now called "title"
function k8sResourceAsPlaylist(r: K8sPlaylist): Playlist {
return {
...r.spec,
uid: r.metadata.name, // replace the uid from the k8s name
};
}

/** 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) {
/** @deprecated -- this migrates playlists saved with internal ids to uid */
async function migrateInternalIDs(playlist: Playlist) {
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) {
if (uids?.length) {
item.value = uids[0];
}
}
}
}
return playlist;
}

export async function legacyGetAllPlaylist(): Promise<Playlist[]> {
return getBackendSrv().get<Playlist[]>('/api/playlists/');
}

async function withErrorHandling(apiCall: () => Promise<void>, message = 'Playlist saved') {
Expand Down Expand Up @@ -158,3 +219,7 @@ export function searchPlaylists(playlists: Playlist[], query?: string): Playlist
query = query.toLowerCase();
return playlists.filter((v) => v.name.toLowerCase().includes(query!));
}

export function getPlaylistAPI() {
return config.featureToggles.kubernetesPlaylists ? new K8sAPI() : new LegacyAPI();
}
Loading

0 comments on commit 6983af3

Please sign in to comment.