mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Scenes: List annotations in dashboard settings (#80995)
* wip listing/viewing annotations * list annotations in settings * fix tests * PR mods
This commit is contained in:
parent
ce39af21a2
commit
63c7096d32
@ -215,7 +215,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel)
|
|||||||
layers = oldModel.annotations?.list.map((a) => {
|
layers = oldModel.annotations?.list.map((a) => {
|
||||||
// Each annotation query is an individual data layer
|
// Each annotation query is an individual data layer
|
||||||
return new DashboardAnnotationsDataLayer({
|
return new DashboardAnnotationsDataLayer({
|
||||||
key: `annnotations-${a.name}`,
|
key: `annotations-${a.name}`,
|
||||||
query: a,
|
query: a,
|
||||||
name: a.name,
|
name: a.name,
|
||||||
isEnabled: Boolean(a.enable),
|
isEnabled: Boolean(a.enable),
|
||||||
|
@ -0,0 +1,126 @@
|
|||||||
|
import { map, of } from 'rxjs';
|
||||||
|
|
||||||
|
import { DataQueryRequest, DataSourceApi, LoadingState, PanelData } from '@grafana/data';
|
||||||
|
import { SceneDataLayers, SceneGridItem, SceneGridLayout, SceneTimeRange } from '@grafana/scenes';
|
||||||
|
|
||||||
|
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
|
||||||
|
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
||||||
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
|
import { activateFullSceneTree } from '../utils/test-utils';
|
||||||
|
|
||||||
|
import { AnnotationsEditView } from './AnnotationsEditView';
|
||||||
|
|
||||||
|
const getDataSourceSrvSpy = jest.fn();
|
||||||
|
const runRequestMock = jest.fn().mockImplementation((ds: DataSourceApi, request: DataQueryRequest) => {
|
||||||
|
const result: PanelData = {
|
||||||
|
state: LoadingState.Loading,
|
||||||
|
series: [],
|
||||||
|
timeRange: request.range,
|
||||||
|
};
|
||||||
|
|
||||||
|
return of([]).pipe(
|
||||||
|
map(() => {
|
||||||
|
result.state = LoadingState.Done;
|
||||||
|
result.series = [];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
...jest.requireActual('@grafana/runtime'),
|
||||||
|
getDataSourceSrv: () => {
|
||||||
|
getDataSourceSrvSpy();
|
||||||
|
},
|
||||||
|
getRunRequest: () => (ds: DataSourceApi, request: DataQueryRequest) => {
|
||||||
|
return runRequestMock(ds, request);
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
publicDashboardAccessToken: 'ac123',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('AnnotationsEditView', () => {
|
||||||
|
describe('Dashboard annotations state', () => {
|
||||||
|
let annotationsView: AnnotationsEditView;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const result = await buildTestScene();
|
||||||
|
annotationsView = result.annotationsView;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the correct urlKey', () => {
|
||||||
|
expect(annotationsView.getUrlKey()).toBe('annotations');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the scene data layers', () => {
|
||||||
|
const dataLayers = annotationsView.getSceneDataLayers();
|
||||||
|
|
||||||
|
expect(dataLayers).toBeInstanceOf(SceneDataLayers);
|
||||||
|
expect(dataLayers?.state.layers.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the annotations length', () => {
|
||||||
|
expect(annotationsView.getAnnotationsLength()).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function buildTestScene() {
|
||||||
|
const annotationsView = new AnnotationsEditView({});
|
||||||
|
const dashboard = new DashboardScene({
|
||||||
|
$timeRange: new SceneTimeRange({}),
|
||||||
|
title: 'hello',
|
||||||
|
uid: 'dash-1',
|
||||||
|
version: 4,
|
||||||
|
meta: {
|
||||||
|
canEdit: true,
|
||||||
|
},
|
||||||
|
$data: new SceneDataLayers({
|
||||||
|
layers: [
|
||||||
|
new DashboardAnnotationsDataLayer({
|
||||||
|
key: `annotations-test`,
|
||||||
|
query: {
|
||||||
|
enable: true,
|
||||||
|
iconColor: 'red',
|
||||||
|
name: 'test',
|
||||||
|
datasource: {
|
||||||
|
type: 'grafana',
|
||||||
|
uid: '-- Grafana --',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: 'test',
|
||||||
|
isEnabled: true,
|
||||||
|
isHidden: false,
|
||||||
|
}),
|
||||||
|
new AlertStatesDataLayer({
|
||||||
|
key: 'alert-states',
|
||||||
|
name: 'Alert States',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
body: new SceneGridLayout({
|
||||||
|
children: [
|
||||||
|
new SceneGridItem({
|
||||||
|
key: 'griditem-1',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 10,
|
||||||
|
height: 12,
|
||||||
|
body: undefined,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
editview: annotationsView,
|
||||||
|
});
|
||||||
|
|
||||||
|
activateFullSceneTree(dashboard);
|
||||||
|
|
||||||
|
await new Promise((r) => setTimeout(r, 1));
|
||||||
|
|
||||||
|
dashboard.onEnterEditMode();
|
||||||
|
annotationsView.activate();
|
||||||
|
|
||||||
|
return { dashboard, annotationsView };
|
||||||
|
}
|
@ -1,30 +1,107 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { PageLayoutType } from '@grafana/data';
|
import { AnnotationQuery, DataTopic, PageLayoutType } from '@grafana/data';
|
||||||
import { SceneComponentProps, SceneObjectBase } from '@grafana/scenes';
|
import {
|
||||||
|
SceneComponentProps,
|
||||||
|
SceneDataLayerProvider,
|
||||||
|
SceneDataLayers,
|
||||||
|
SceneObjectBase,
|
||||||
|
sceneGraph,
|
||||||
|
} from '@grafana/scenes';
|
||||||
import { Page } from 'app/core/components/Page/Page';
|
import { Page } from 'app/core/components/Page/Page';
|
||||||
|
|
||||||
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
import { NavToolbarActions } from '../scene/NavToolbarActions';
|
import { NavToolbarActions } from '../scene/NavToolbarActions';
|
||||||
|
import { dataLayersToAnnotations } from '../serialization/dataLayersToAnnotations';
|
||||||
import { getDashboardSceneFor } from '../utils/utils';
|
import { getDashboardSceneFor } from '../utils/utils';
|
||||||
|
|
||||||
|
import { EditListViewSceneUrlSync } from './EditListViewSceneUrlSync';
|
||||||
|
import { AnnotationSettingsList } from './annotations';
|
||||||
import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils';
|
import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils';
|
||||||
|
|
||||||
export interface AnnotationsEditViewState extends DashboardEditViewState {}
|
export interface AnnotationsEditViewState extends DashboardEditViewState {
|
||||||
|
editIndex?: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export class AnnotationsEditView extends SceneObjectBase<AnnotationsEditViewState> implements DashboardEditView {
|
export class AnnotationsEditView extends SceneObjectBase<AnnotationsEditViewState> implements DashboardEditView {
|
||||||
|
static Component = AnnotationsSettingsView;
|
||||||
|
|
||||||
public getUrlKey(): string {
|
public getUrlKey(): string {
|
||||||
return 'annotations';
|
return 'annotations';
|
||||||
}
|
}
|
||||||
|
|
||||||
static Component = ({ model }: SceneComponentProps<AnnotationsEditView>) => {
|
protected _urlSync = new EditListViewSceneUrlSync(this);
|
||||||
const dashboard = getDashboardSceneFor(model);
|
|
||||||
|
private get _dashboard(): DashboardScene {
|
||||||
|
return getDashboardSceneFor(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _dataLayers(): SceneDataLayerProvider[] {
|
||||||
|
return sceneGraph.getDataLayers(this._dashboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSceneDataLayers(): SceneDataLayers | undefined {
|
||||||
|
const data = sceneGraph.getData(this);
|
||||||
|
|
||||||
|
if (!(data instanceof SceneDataLayers)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAnnotationsLength(): number {
|
||||||
|
return this._dataLayers.filter((layer) => layer.topic === DataTopic.Annotations).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDashboard(): DashboardScene {
|
||||||
|
return this._dashboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onNew = () => {
|
||||||
|
console.log('todo: onNew');
|
||||||
|
};
|
||||||
|
|
||||||
|
public onEdit = (idx: number) => {
|
||||||
|
console.log('todo: onEdit');
|
||||||
|
};
|
||||||
|
|
||||||
|
public onMove = (idx: number, direction: number) => {
|
||||||
|
console.log('todo: onMove');
|
||||||
|
};
|
||||||
|
|
||||||
|
public onDelete = (idx: number) => {
|
||||||
|
console.log('todo: onDelete');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function AnnotationsSettingsView({ model }: SceneComponentProps<AnnotationsEditView>) {
|
||||||
|
const dashboard = model.getDashboard();
|
||||||
|
const sceneDataLayers = model.getSceneDataLayers();
|
||||||
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
||||||
|
const { editIndex } = model.useState();
|
||||||
|
|
||||||
|
let annotations: AnnotationQuery[] = [];
|
||||||
|
|
||||||
|
if (sceneDataLayers) {
|
||||||
|
const { layers } = sceneDataLayers.useState();
|
||||||
|
annotations = dataLayersToAnnotations(layers);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEditing = editIndex != null && editIndex < model.getAnnotationsLength();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
||||||
<NavToolbarActions dashboard={dashboard} />
|
<NavToolbarActions dashboard={dashboard} />
|
||||||
<div>Annotations todo</div>
|
{!isEditing && (
|
||||||
|
<AnnotationSettingsList
|
||||||
|
annotations={annotations}
|
||||||
|
onNew={model.onNew}
|
||||||
|
onEdit={model.onEdit}
|
||||||
|
onDelete={model.onDelete}
|
||||||
|
onMove={model.onMove}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { SceneObjectUrlSyncHandler, SceneObjectUrlValues } from '@grafana/scenes';
|
import { SceneObjectUrlSyncHandler, SceneObjectUrlValues } from '@grafana/scenes';
|
||||||
|
|
||||||
|
import { AnnotationsEditView, AnnotationsEditViewState } from './AnnotationsEditView';
|
||||||
import { DashboardLinksEditView, DashboardLinksEditViewState } from './DashboardLinksEditView';
|
import { DashboardLinksEditView, DashboardLinksEditViewState } from './DashboardLinksEditView';
|
||||||
import { VariablesEditView, VariablesEditViewState } from './VariablesEditView';
|
import { VariablesEditView, VariablesEditViewState } from './VariablesEditView';
|
||||||
|
|
||||||
type EditListViewUrlSync = DashboardLinksEditView | VariablesEditView;
|
type EditListViewUrlSync = DashboardLinksEditView | VariablesEditView | AnnotationsEditView;
|
||||||
type EditListViewState = DashboardLinksEditViewState | VariablesEditViewState;
|
type EditListViewState = DashboardLinksEditViewState | VariablesEditViewState | AnnotationsEditViewState;
|
||||||
export class EditListViewSceneUrlSync implements SceneObjectUrlSyncHandler {
|
export class EditListViewSceneUrlSync implements SceneObjectUrlSyncHandler {
|
||||||
constructor(private _scene: EditListViewUrlSync) {}
|
constructor(private _scene: EditListViewUrlSync) {}
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ function getVersions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function buildTestScene() {
|
async function buildTestScene() {
|
||||||
const versionsView = new VersionsEditView({ versions: [] });
|
const versionsView = new VersionsEditView({});
|
||||||
const dashboard = new DashboardScene({
|
const dashboard = new DashboardScene({
|
||||||
$timeRange: new SceneTimeRange({}),
|
$timeRange: new SceneTimeRange({}),
|
||||||
title: 'hello',
|
title: 'hello',
|
||||||
|
@ -0,0 +1,121 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { AnnotationQuery } from '@grafana/data';
|
||||||
|
import { getDataSourceSrv } from '@grafana/runtime';
|
||||||
|
import { Button, DeleteButton, IconButton, useStyles2, VerticalGroup } from '@grafana/ui';
|
||||||
|
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||||
|
import { ListNewButton } from 'app/features/dashboard/components/DashboardSettings/ListNewButton';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
annotations: AnnotationQuery[];
|
||||||
|
onNew: () => void;
|
||||||
|
onEdit: (idx: number) => void;
|
||||||
|
onMove: (idx: number, dir: number) => void;
|
||||||
|
onDelete: (idx: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AnnotationSettingsList = ({ annotations, onNew, onEdit, onMove, onDelete }: Props) => {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
const showEmptyListCTA = annotations.length === 0 || (annotations.length === 1 && annotations[0].builtIn);
|
||||||
|
|
||||||
|
const getAnnotationName = (anno: AnnotationQuery) => {
|
||||||
|
if (anno.enable === false) {
|
||||||
|
return <em className="muted">(Disabled) {anno.name}</em>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anno.builtIn) {
|
||||||
|
return <em className="muted">{anno.name} (Built-in)</em>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{anno.name}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataSourceSrv = getDataSourceSrv();
|
||||||
|
return (
|
||||||
|
<VerticalGroup>
|
||||||
|
{annotations.length > 0 && (
|
||||||
|
<div className={styles.table}>
|
||||||
|
<table role="grid" className="filter-table filter-table--hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Query name</th>
|
||||||
|
<th>Data source</th>
|
||||||
|
<th colSpan={3}></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{annotations.map((annotation, idx) => (
|
||||||
|
<tr key={`${annotation.name}-${idx}`}>
|
||||||
|
{annotation.builtIn ? (
|
||||||
|
<td role="gridcell" style={{ width: '90%' }} className="pointer" onClick={() => onEdit(idx)}>
|
||||||
|
<Button size="sm" fill="text" variant="secondary">
|
||||||
|
{getAnnotationName(annotation)}
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
) : (
|
||||||
|
<td role="gridcell" className="pointer" onClick={() => onEdit(idx)}>
|
||||||
|
<Button size="sm" fill="text" variant="secondary">
|
||||||
|
{getAnnotationName(annotation)}
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
<td role="gridcell" className="pointer" onClick={() => onEdit(idx)}>
|
||||||
|
{dataSourceSrv.getInstanceSettings(annotation.datasource)?.name || annotation.datasource?.uid}
|
||||||
|
</td>
|
||||||
|
<td role="gridcell" style={{ width: '1%' }}>
|
||||||
|
{idx !== 0 && <IconButton name="arrow-up" onClick={() => onMove(idx, -1)} tooltip="Move up" />}
|
||||||
|
</td>
|
||||||
|
<td role="gridcell" style={{ width: '1%' }}>
|
||||||
|
{annotations.length > 1 && idx !== annotations.length - 1 ? (
|
||||||
|
<IconButton name="arrow-down" onClick={() => onMove(idx, 1)} tooltip="Move down" />
|
||||||
|
) : null}
|
||||||
|
</td>
|
||||||
|
<td role="gridcell" style={{ width: '1%' }}>
|
||||||
|
{!annotation.builtIn && (
|
||||||
|
<DeleteButton
|
||||||
|
size="sm"
|
||||||
|
onConfirm={() => onDelete(idx)}
|
||||||
|
aria-label={`Delete query with title "${annotation.name}"`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{showEmptyListCTA && (
|
||||||
|
<EmptyListCTA
|
||||||
|
onClick={onNew}
|
||||||
|
title="There are no custom annotation queries added yet"
|
||||||
|
buttonIcon="comment-alt"
|
||||||
|
buttonTitle="Add annotation query"
|
||||||
|
infoBoxTitle="What are annotation queries?"
|
||||||
|
infoBox={{
|
||||||
|
__html: `<p>Annotations provide a way to integrate event data into your graphs. They are visualized as vertical lines
|
||||||
|
and icons on all graph panels. When you hover over an annotation icon you can get event text & tags for
|
||||||
|
the event. You can add annotation events directly from grafana by holding CTRL or CMD + click on graph (or
|
||||||
|
drag region). These will be stored in Grafana's annotation database.
|
||||||
|
</p>
|
||||||
|
Checkout the
|
||||||
|
<a class='external-link' target='_blank' href='http://docs.grafana.org/reference/annotations/'
|
||||||
|
>Annotations documentation</a
|
||||||
|
>
|
||||||
|
for more information.`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!showEmptyListCTA && <ListNewButton onClick={onNew}>New query</ListNewButton>}
|
||||||
|
</VerticalGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = () => ({
|
||||||
|
table: css({
|
||||||
|
width: '100%',
|
||||||
|
overflowX: 'scroll',
|
||||||
|
}),
|
||||||
|
});
|
@ -0,0 +1 @@
|
|||||||
|
export { AnnotationSettingsList } from './AnnotationSettingsList';
|
@ -9,10 +9,13 @@ import {
|
|||||||
VizPanel,
|
VizPanel,
|
||||||
SceneTimePicker,
|
SceneTimePicker,
|
||||||
SceneDataTransformer,
|
SceneDataTransformer,
|
||||||
|
SceneDataLayers,
|
||||||
} from '@grafana/scenes';
|
} from '@grafana/scenes';
|
||||||
import { DashboardCursorSync } from '@grafana/schema';
|
import { DashboardCursorSync } from '@grafana/schema';
|
||||||
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
|
import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
|
||||||
|
|
||||||
|
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
|
||||||
|
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
||||||
import { DashboardControls } from '../scene/DashboardControls';
|
import { DashboardControls } from '../scene/DashboardControls';
|
||||||
import { DashboardLinksControls } from '../scene/DashboardLinksControls';
|
import { DashboardLinksControls } from '../scene/DashboardLinksControls';
|
||||||
import { DashboardScene } from '../scene/DashboardScene';
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
@ -38,6 +41,9 @@ describe('DashboardModelCompatibilityWrapper', () => {
|
|||||||
expect(wrapper.timepicker.hidden).toEqual(true);
|
expect(wrapper.timepicker.hidden).toEqual(true);
|
||||||
expect(wrapper.panels).toHaveLength(5);
|
expect(wrapper.panels).toHaveLength(5);
|
||||||
|
|
||||||
|
expect(wrapper.annotations.list).toHaveLength(1);
|
||||||
|
expect(wrapper.annotations.list[0].name).toBe('test');
|
||||||
|
|
||||||
expect(wrapper.panels[0].targets).toHaveLength(1);
|
expect(wrapper.panels[0].targets).toHaveLength(1);
|
||||||
expect(wrapper.panels[0].targets[0]).toEqual({ refId: 'A' });
|
expect(wrapper.panels[0].targets[0]).toEqual({ refId: 'A' });
|
||||||
expect(wrapper.panels[1].targets).toHaveLength(0);
|
expect(wrapper.panels[1].targets).toHaveLength(0);
|
||||||
@ -104,6 +110,22 @@ describe('DashboardModelCompatibilityWrapper', () => {
|
|||||||
|
|
||||||
expect((scene.state.body as SceneGridLayout).state.children.length).toBe(4);
|
expect((scene.state.body as SceneGridLayout).state.children.length).toBe(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Checks if annotations are editable', () => {
|
||||||
|
const { wrapper, scene } = setup();
|
||||||
|
|
||||||
|
expect(wrapper.canEditAnnotations()).toBe(true);
|
||||||
|
expect(wrapper.canEditAnnotations(scene.state.uid)).toBe(false);
|
||||||
|
|
||||||
|
scene.setState({
|
||||||
|
meta: {
|
||||||
|
canEdit: false,
|
||||||
|
canMakeEditable: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.canEditAnnotations()).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
@ -114,10 +136,45 @@ function setup() {
|
|||||||
links: [NEW_LINK],
|
links: [NEW_LINK],
|
||||||
uid: 'dash-1',
|
uid: 'dash-1',
|
||||||
editable: false,
|
editable: false,
|
||||||
|
meta: {
|
||||||
|
canEdit: true,
|
||||||
|
canMakeEditable: true,
|
||||||
|
annotationsPermissions: {
|
||||||
|
organization: {
|
||||||
|
canEdit: true,
|
||||||
|
canAdd: true,
|
||||||
|
canDelete: true,
|
||||||
|
},
|
||||||
|
dashboard: {
|
||||||
|
canEdit: false,
|
||||||
|
canAdd: false,
|
||||||
|
canDelete: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
$timeRange: new SceneTimeRange({
|
$timeRange: new SceneTimeRange({
|
||||||
weekStart: 'friday',
|
weekStart: 'friday',
|
||||||
timeZone: 'America/New_York',
|
timeZone: 'America/New_York',
|
||||||
}),
|
}),
|
||||||
|
$data: new SceneDataLayers({
|
||||||
|
layers: [
|
||||||
|
new DashboardAnnotationsDataLayer({
|
||||||
|
key: `annotations-test`,
|
||||||
|
query: {
|
||||||
|
enable: true,
|
||||||
|
iconColor: 'red',
|
||||||
|
name: 'test',
|
||||||
|
},
|
||||||
|
name: 'test',
|
||||||
|
isEnabled: true,
|
||||||
|
isHidden: false,
|
||||||
|
}),
|
||||||
|
new AlertStatesDataLayer({
|
||||||
|
key: 'alert-states',
|
||||||
|
name: 'Alert States',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
controls: [
|
controls: [
|
||||||
new DashboardControls({
|
new DashboardControls({
|
||||||
variableControls: [],
|
variableControls: [],
|
||||||
|
@ -4,6 +4,7 @@ import { AnnotationQuery, DashboardCursorSync, dateTimeFormat, DateTimeInput, Ev
|
|||||||
import { TimeRangeUpdatedEvent } from '@grafana/runtime';
|
import { TimeRangeUpdatedEvent } from '@grafana/runtime';
|
||||||
import {
|
import {
|
||||||
behaviors,
|
behaviors,
|
||||||
|
SceneDataLayers,
|
||||||
SceneDataTransformer,
|
SceneDataTransformer,
|
||||||
sceneGraph,
|
sceneGraph,
|
||||||
SceneGridItem,
|
SceneGridItem,
|
||||||
@ -16,6 +17,7 @@ import { DataSourceRef } from '@grafana/schema';
|
|||||||
|
|
||||||
import { DashboardScene } from '../scene/DashboardScene';
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||||
|
import { dataLayersToAnnotations } from '../serialization/dataLayersToAnnotations';
|
||||||
|
|
||||||
import { dashboardSceneGraph } from './dashboardSceneGraph';
|
import { dashboardSceneGraph } from './dashboardSceneGraph';
|
||||||
import { findVizPanelByKey, getPanelIdForVizPanel, getQueryRunnerFor, getVizPanelKeyForPanelId } from './utils';
|
import { findVizPanelByKey, getPanelIdForVizPanel, getQueryRunnerFor, getVizPanelKeyForPanelId } from './utils';
|
||||||
@ -109,8 +111,13 @@ export class DashboardModelCompatibilityWrapper {
|
|||||||
* Used from from timeseries migration handler to migrate time regions to dashboard annotations
|
* Used from from timeseries migration handler to migrate time regions to dashboard annotations
|
||||||
*/
|
*/
|
||||||
public get annotations(): { list: AnnotationQuery[] } {
|
public get annotations(): { list: AnnotationQuery[] } {
|
||||||
console.error('Scenes DashboardModelCompatibilityWrapper.annotations not implemented (yet)');
|
const annotations: { list: AnnotationQuery[] } = { list: [] };
|
||||||
return { list: [] };
|
|
||||||
|
if (this._scene.state.$data instanceof SceneDataLayers) {
|
||||||
|
annotations.list = dataLayersToAnnotations(this._scene.state.$data.state.layers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return annotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTimezone() {
|
public getTimezone() {
|
||||||
@ -204,10 +211,17 @@ export class DashboardModelCompatibilityWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public canEditAnnotations(dashboardUID?: string) {
|
public canEditAnnotations(dashboardUID?: string) {
|
||||||
// TOOD
|
if (!this._scene.canEditDashboard()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dashboardUID) {
|
||||||
|
return Boolean(this._scene.state.meta.annotationsPermissions?.dashboard.canEdit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Boolean(this._scene.state.meta.annotationsPermissions?.organization.canEdit);
|
||||||
|
}
|
||||||
|
|
||||||
public panelInitialized() {}
|
public panelInitialized() {}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
|
Loading…
Reference in New Issue
Block a user