mirror of https://github.com/grafana/grafana
PlayList: add delete functionality to remove playlist (#38120)
* WIP: add delete functionality to playlist * fixes deleted item to be removed instantly without manual refresh * update confirmModal to reference playlist name * refactor confirmModal message to be clear enough * WIP: some unit tests for the playlistPage * added more tests and did some cleanup * some code refactoring * adds ability for user roles to control playlist delete * some abstraction to cleanup code * modified alert message for delete button to correspond with action * tried a better approach to modify the alert message * fixes playlist lookup on each render * update handlers to not use anonymous function * exposed getBackendSrv().get api to fetch all playlist * used better naming convention * removes unecessary async/await construct * some code refactoring * used the correct param structurepull/38787/head
parent
326455a9b8
commit
4e8ab0512c
@ -0,0 +1,83 @@ |
||||
import React from 'react'; |
||||
import { render, waitFor } from '@testing-library/react'; |
||||
import { PlaylistPage, PlaylistPageProps } from './PlaylistPage'; |
||||
import { locationService } from '../../../../packages/grafana-runtime/src'; |
||||
|
||||
const fnMock = jest.fn(); |
||||
|
||||
jest.mock('@grafana/runtime', () => ({ |
||||
...((jest.requireActual('@grafana/runtime') as unknown) as object), |
||||
getBackendSrv: () => ({ |
||||
get: fnMock, |
||||
}), |
||||
})); |
||||
|
||||
jest.mock('app/core/services/context_srv', () => ({ |
||||
contextSrv: { |
||||
isEditor: true, |
||||
}, |
||||
})); |
||||
|
||||
function getTestContext(propOverrides?: object) { |
||||
const props: PlaylistPageProps = { |
||||
navModel: { |
||||
main: { |
||||
text: 'Playlist', |
||||
}, |
||||
node: { |
||||
text: 'playlist', |
||||
}, |
||||
}, |
||||
route: { |
||||
path: '/playlists', |
||||
component: jest.fn(), |
||||
}, |
||||
queryParams: { state: 'ok' }, |
||||
match: { params: { name: 'playlist', sourceName: 'test playlist' }, isExact: false, url: 'asdf', path: '' }, |
||||
history: locationService.getHistory(), |
||||
location: { pathname: '', hash: '', search: '', state: '' }, |
||||
}; |
||||
|
||||
Object.assign(props, propOverrides); |
||||
|
||||
return render(<PlaylistPage {...props} />); |
||||
} |
||||
|
||||
describe('PlaylistPage', () => { |
||||
describe('when mounted without a playlist', () => { |
||||
it('page should load', () => { |
||||
fnMock.mockResolvedValue([]); |
||||
const { getByText } = getTestContext(); |
||||
expect(getByText(/loading/i)).toBeInTheDocument(); |
||||
}); |
||||
it('then show empty list', async () => { |
||||
const { getByText } = getTestContext(); |
||||
await waitFor(() => getByText('There are no playlists created yet')); |
||||
}); |
||||
}); |
||||
describe('when mounted with a playlist', () => { |
||||
it('page should load', () => { |
||||
fnMock.mockResolvedValue([ |
||||
{ |
||||
id: 0, |
||||
name: 'A test playlist', |
||||
interval: '10m', |
||||
items: [ |
||||
{ title: 'First item', type: 'dashboard_by_id', order: 1, value: '1' }, |
||||
{ title: 'Middle item', type: 'dashboard_by_id', order: 2, value: '2' }, |
||||
{ title: 'Last item', type: 'dashboard_by_tag', order: 2, value: 'Last item' }, |
||||
], |
||||
}, |
||||
]); |
||||
const { getByText } = getTestContext(); |
||||
expect(getByText(/loading/i)).toBeInTheDocument(); |
||||
}); |
||||
it('then playlist title and buttons should appear on the page', async () => { |
||||
const { getByRole, getByText } = getTestContext(); |
||||
await waitFor(() => getByText('A test playlist')); |
||||
expect(getByRole('button', { name: /Start playlist/i })).toBeInTheDocument(); |
||||
expect(getByRole('link', { name: /Edit playlist/i })).toBeInTheDocument(); |
||||
expect(getByRole('button', { name: /Delete playlist/i })).toBeInTheDocument(); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,54 @@ |
||||
import React, { FC, useState } from 'react'; |
||||
import { SelectableValue, urlUtil } from '@grafana/data'; |
||||
import { locationService } from '@grafana/runtime'; |
||||
import { PlaylistDTO } from './types'; |
||||
import { Button, Checkbox, Field, Modal, RadioButtonGroup, VerticalGroup } from '@grafana/ui'; |
||||
|
||||
export interface StartModalProps { |
||||
playlist: PlaylistDTO; |
||||
onDismiss: () => void; |
||||
} |
||||
|
||||
export const StartModal: FC<StartModalProps> = ({ playlist, onDismiss }) => { |
||||
const [mode, setMode] = useState<any>(false); |
||||
const [autoFit, setAutofit] = useState(false); |
||||
|
||||
const modes: Array<SelectableValue<any>> = [ |
||||
{ label: 'Normal', value: false }, |
||||
{ label: 'TV', value: 'tv' }, |
||||
{ label: 'Kiosk', value: true }, |
||||
]; |
||||
|
||||
const onStart = () => { |
||||
const params: any = {}; |
||||
if (mode) { |
||||
params.kiosk = mode; |
||||
} |
||||
if (autoFit) { |
||||
params.autofitpanels = true; |
||||
} |
||||
locationService.push(urlUtil.renderUrl(`/playlists/play/${playlist.id}`, params)); |
||||
}; |
||||
|
||||
return ( |
||||
<Modal isOpen={true} icon="play" title="Start playlist" onDismiss={onDismiss}> |
||||
<VerticalGroup> |
||||
<Field label="Mode"> |
||||
<RadioButtonGroup value={mode} options={modes} onChange={setMode} /> |
||||
</Field> |
||||
<Checkbox |
||||
label="Autofit" |
||||
description="Panel heights will be adjusted to fit screen size" |
||||
name="autofix" |
||||
value={autoFit} |
||||
onChange={(e) => setAutofit(e.currentTarget.checked)} |
||||
/> |
||||
</VerticalGroup> |
||||
<Modal.ButtonRow> |
||||
<Button variant="primary" onClick={onStart}> |
||||
Start {playlist.name} |
||||
</Button> |
||||
</Modal.ButtonRow> |
||||
</Modal> |
||||
); |
||||
}; |
Loading…
Reference in new issue