Commit 6599bdc7 by Ryan McKinley Committed by GitHub

AnnoList: add alpha annotations list plugin (#17187)

adding an alpha panel
parent b44be990
......@@ -97,6 +97,9 @@ export interface AnnotationEvent {
dashboardId?: number;
panelId?: number;
userId?: number;
login?: string;
email?: string;
avatarUrl?: string;
time?: number;
timeEnd?: number;
isRegion?: boolean;
......
......@@ -11,9 +11,14 @@ import { TagBadge } from './TagBadge';
import { NoOptionsMessage, IndicatorsContainer, resetSelectStyles } from '@grafana/ui';
import { escapeStringForRegex } from '../FilterInput/FilterInput';
export interface TermCount {
term: string;
count: number;
}
export interface Props {
tags: string[];
tagOptions: () => any;
tagOptions: () => Promise<TermCount[]>;
onChange: (tags: string[]) => void;
}
......@@ -25,7 +30,7 @@ export class TagFilter extends React.Component<Props, any> {
}
onLoadOptions = (query: string) => {
return this.props.tagOptions().then((options: any[]) => {
return this.props.tagOptions().then(options => {
return options.map(option => ({
value: option.term,
label: option.term,
......
......@@ -22,6 +22,7 @@ import * as graphPanel from 'app/plugins/panel/graph/module';
import * as dashListPanel from 'app/plugins/panel/dashlist/module';
import * as pluginsListPanel from 'app/plugins/panel/pluginlist/module';
import * as alertListPanel from 'app/plugins/panel/alertlist/module';
import * as annoListPanel from 'app/plugins/panel/annolist/module';
import * as heatmapPanel from 'app/plugins/panel/heatmap/module';
import * as tablePanel from 'app/plugins/panel/table/module';
import * as table2Panel from 'app/plugins/panel/table2/module';
......@@ -59,6 +60,7 @@ const builtInPlugins = {
'app/plugins/panel/dashlist/module': dashListPanel,
'app/plugins/panel/pluginlist/module': pluginsListPanel,
'app/plugins/panel/alertlist/module': alertListPanel,
'app/plugins/panel/annolist/module': annoListPanel,
'app/plugins/panel/heatmap/module': heatmapPanel,
'app/plugins/panel/table/module': tablePanel,
'app/plugins/panel/table2/module': table2Panel,
......
// Libraries
import React, { PureComponent, ChangeEvent } from 'react';
// Components
import { PanelEditorProps, PanelOptionsGroup, PanelOptionsGrid, Switch, FormField, FormLabel } from '@grafana/ui';
import { toIntegerOrUndefined, toNumberString } from '@grafana/data';
// Types
import { AnnoOptions } from './types';
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
interface State {
tag: string;
}
export class AnnoListEditor extends PureComponent<PanelEditorProps<AnnoOptions>, State> {
constructor(props: PanelEditorProps<AnnoOptions>) {
super(props);
this.state = {
tag: '',
};
}
// Display
//-----------
onToggleShowUser = () =>
this.props.onOptionsChange({ ...this.props.options, showUser: !this.props.options.showUser });
onToggleShowTime = () =>
this.props.onOptionsChange({ ...this.props.options, showTime: !this.props.options.showTime });
onToggleShowTags = () =>
this.props.onOptionsChange({ ...this.props.options, showTags: !this.props.options.showTags });
// Navigate
//-----------
onNavigateBeforeChange = (event: ChangeEvent<HTMLInputElement>) => {
this.props.onOptionsChange({ ...this.props.options, navigateBefore: event.target.value });
};
onNavigateAfterChange = (event: ChangeEvent<HTMLInputElement>) => {
this.props.onOptionsChange({ ...this.props.options, navigateAfter: event.target.value });
};
onToggleNavigateToPanel = () =>
this.props.onOptionsChange({ ...this.props.options, navigateToPanel: !this.props.options.navigateToPanel });
// Search
//-----------
onLimitChange = (event: ChangeEvent<HTMLInputElement>) => {
const v = toIntegerOrUndefined(event.target.value);
this.props.onOptionsChange({ ...this.props.options, limit: v });
};
onToggleOnlyFromThisDashboard = () =>
this.props.onOptionsChange({
...this.props.options,
onlyFromThisDashboard: !this.props.options.onlyFromThisDashboard,
});
onToggleOnlyInTimeRange = () =>
this.props.onOptionsChange({ ...this.props.options, onlyInTimeRange: !this.props.options.onlyInTimeRange });
// Tags
//-----------
onTagTextChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({ tag: event.target.value });
};
onTagClick = (e: React.SyntheticEvent, tag: string) => {
e.stopPropagation();
const tags = this.props.options.tags.filter(item => item !== tag);
this.props.onOptionsChange({
...this.props.options,
tags,
});
};
renderTags = (tags: string[]): JSX.Element => {
if (!tags || !tags.length) {
return null;
}
return (
<>
{tags.map(tag => {
return (
<span key={tag} onClick={e => this.onTagClick(e, tag)} className="pointer">
<TagBadge label={tag} removeIcon={true} count={0} />
</span>
);
})}
</>
);
};
render() {
const { options } = this.props;
const labelWidth = 8;
return (
<PanelOptionsGrid>
<PanelOptionsGroup title="Display">
<Switch
label="Show User"
labelClass={`width-${labelWidth}`}
checked={options.showUser}
onChange={this.onToggleShowUser}
/>
<Switch
label="Show Time"
labelClass={`width-${labelWidth}`}
checked={options.showTime}
onChange={this.onToggleShowTime}
/>
<Switch
label="Show Tags"
labelClass={`width-${labelWidth}`}
checked={options.showTags}
onChange={this.onToggleShowTags}
/>
</PanelOptionsGroup>
<PanelOptionsGroup title="Navigate">
<FormField
label="Before"
labelWidth={labelWidth}
onChange={this.onNavigateBeforeChange}
value={options.navigateBefore}
/>
<FormField
label="After"
labelWidth={labelWidth}
onChange={this.onNavigateAfterChange}
value={options.navigateAfter}
/>
<Switch
label="To Panel"
labelClass={`width-${labelWidth}`}
checked={options.navigateToPanel}
onChange={this.onToggleNavigateToPanel}
/>
</PanelOptionsGroup>
<PanelOptionsGroup title="Search">
<Switch
label="Only This Dashboard"
labelClass={`width-12`}
checked={options.onlyFromThisDashboard}
onChange={this.onToggleOnlyFromThisDashboard}
/>
<Switch
label="Within Time Range"
labelClass={`width-12`}
checked={options.onlyInTimeRange}
onChange={this.onToggleOnlyInTimeRange}
/>
<div className="form-field">
<FormLabel width={6}>Tags</FormLabel>
{this.renderTags(options.tags)}
<input
type="text"
className={`gf-form-input width-${8}`}
value={this.state.tag}
onChange={this.onTagTextChange}
onKeyPress={ev => {
if (this.state.tag && ev.key === 'Enter') {
const tags = [...options.tags, this.state.tag];
this.props.onOptionsChange({
...this.props.options,
tags,
});
this.setState({ tag: '' });
ev.preventDefault();
}
}}
/>
</div>
<FormField
label="Limit"
labelWidth={6}
onChange={this.onLimitChange}
value={toNumberString(options.limit)}
type="number"
/>
</PanelOptionsGroup>
</PanelOptionsGrid>
);
}
}
// Libraries
import React, { PureComponent } from 'react';
// Types
import { AnnoOptions } from './types';
import { dateTime, DurationUnit, AnnotationEvent } from '@grafana/data';
import { PanelProps, Tooltip } from '@grafana/ui';
import { getBackendSrv } from 'app/core/services/backend_srv';
import { AbstractList } from '@grafana/ui/src/components/List/AbstractList';
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import appEvents from 'app/core/app_events';
import { updateLocation } from 'app/core/actions';
import { store } from 'app/store/store';
import { cx, css } from 'emotion';
interface UserInfo {
id: number;
login: string;
email: string;
}
interface Props extends PanelProps<AnnoOptions> {}
interface State {
annotations: AnnotationEvent[];
timeInfo: string;
loaded: boolean;
queryUser?: UserInfo;
queryTags: string[];
}
export class AnnoListPanel extends PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
annotations: [],
timeInfo: '',
loaded: false,
queryTags: [],
};
}
componentDidMount() {
this.doSearch();
}
componentDidUpdate(prevProps: Props, prevState: State) {
const { options, timeRange } = this.props;
const needsQuery =
options !== prevProps.options ||
this.state.queryTags !== prevState.queryTags ||
this.state.queryUser !== prevState.queryUser ||
timeRange !== prevProps.timeRange;
if (needsQuery) {
this.doSearch();
}
}
async doSearch() {
// http://docs.grafana.org/http_api/annotations/
// https://github.com/grafana/grafana/blob/master/public/app/core/services/backend_srv.ts
// https://github.com/grafana/grafana/blob/master/public/app/features/annotations/annotations_srv.ts
const { options } = this.props;
const { queryUser, queryTags } = this.state;
const params: any = {
tags: options.tags,
limit: options.limit,
type: 'annotation', // Skip the Annotations that are really alerts. (Use the alerts panel!)
};
if (options.onlyFromThisDashboard) {
params.dashboardId = getDashboardSrv().getCurrent().id;
}
let timeInfo = '';
if (options.onlyInTimeRange) {
const { timeRange } = this.props;
params.from = timeRange.from.valueOf();
params.to = timeRange.to.valueOf();
} else {
timeInfo = 'All Time';
}
if (queryUser) {
params.userId = queryUser.id;
}
if (options.tags && options.tags.length) {
params.tags = options.tags;
}
if (queryTags.length) {
params.tags = params.tags ? [...params.tags, ...queryTags] : queryTags;
}
const annotations = await getBackendSrv().get('/api/annotations', params);
this.setState({
annotations,
timeInfo,
loaded: true,
});
}
onAnnoClick = (e: React.SyntheticEvent, anno: AnnotationEvent) => {
e.stopPropagation();
const { options } = this.props;
const dashboardSrv = getDashboardSrv();
const current = dashboardSrv.getCurrent();
const params: any = {
from: this._timeOffset(anno.time, options.navigateBefore, true),
to: this._timeOffset(anno.time, options.navigateAfter, false),
};
if (options.navigateToPanel) {
params.panelId = anno.panelId;
params.fullscreen = true;
}
if (current.id === anno.dashboardId) {
store.dispatch(
updateLocation({
query: params,
partial: true,
})
);
return;
}
getBackendSrv()
.get('/api/search', { dashboardIds: anno.dashboardId })
.then((res: any[]) => {
if (res && res.length && res[0].id === anno.dashboardId) {
const dash = res[0];
store.dispatch(
updateLocation({
query: params,
path: dash.url,
})
);
return;
}
appEvents.emit('alert-warning', ['Unknown Dashboard: ' + anno.dashboardId]);
});
};
_timeOffset(time: number, offset: string, subtract = false): number {
let incr = 5;
let unit = 'm';
const parts = /^(\d+)(\w)/.exec(offset);
if (parts && parts.length === 3) {
incr = parseInt(parts[1], 10);
unit = parts[2];
}
const t = dateTime(time);
if (subtract) {
incr *= -1;
}
return t.add(incr, unit as DurationUnit).valueOf();
}
onTagClick = (e: React.SyntheticEvent, tag: string, remove: boolean) => {
e.stopPropagation();
const queryTags = remove ? this.state.queryTags.filter(item => item !== tag) : [...this.state.queryTags, tag];
this.setState({ queryTags });
};
onUserClick = (e: React.SyntheticEvent, anno: AnnotationEvent) => {
e.stopPropagation();
this.setState({
queryUser: {
id: anno.userId,
login: anno.login,
email: anno.email,
},
});
};
onClearUser = () => {
this.setState({
queryUser: undefined,
});
};
renderTags = (tags: string[], remove: boolean): JSX.Element => {
if (!tags || !tags.length) {
return null;
}
return (
<>
{tags.map(tag => {
return (
<span key={tag} onClick={e => this.onTagClick(e, tag, remove)} className="pointer">
<TagBadge label={tag} removeIcon={remove} count={0} />
</span>
);
})}
</>
);
};
renderItem = (anno: AnnotationEvent, index: number): JSX.Element => {
const { options } = this.props;
const { showUser, showTags, showTime } = options;
const dashboard = getDashboardSrv().getCurrent();
return (
<div className="dashlist-item">
<span
className="dashlist-link pointer"
onClick={e => {
this.onAnnoClick(e, anno);
}}
>
<span
className={cx([
'dashlist-title',
css`
margin-right: 8px;
`,
])}
>
{anno.text}
</span>
<span className="pluginlist-message">
{anno.login && showUser && (
<span className="graph-annotation">
<Tooltip
content={
<span>
Created by:
<br /> {anno.email}
</span>
}
theme="info"
placement="top"
>
<span onClick={e => this.onUserClick(e, anno)} className="graph-annotation__user">
<img src={anno.avatarUrl} />
</span>
</Tooltip>
</span>
)}
{showTags && this.renderTags(anno.tags, false)}
</span>
<span className="pluginlist-version">{showTime && <span>{dashboard.formatDate(anno.time)}</span>}</span>
</span>
</div>
);
};
render() {
const { height } = this.props;
const { loaded, annotations, queryUser, queryTags } = this.state;
if (!loaded) {
return <div>loading...</div>;
}
// Previously we showed inidication that it covered all time
// { timeInfo && (
// <span className="panel-time-info">
// <i className="fa fa-clock-o" /> {timeInfo}
// </span>
// )}
const hasFilter = queryUser || queryTags.length > 0;
return (
<div style={{ height, overflow: 'scroll' }}>
{hasFilter && (
<div>
<b>Filter: &nbsp; </b>
{queryUser && (
<span onClick={this.onClearUser} className="pointer">
{queryUser.email}
</span>
)}
{queryTags.length > 0 && this.renderTags(queryTags, true)}
</div>
)}
{annotations.length < 1 && <div className="panel-alert-list__no-alerts">No Annotations Found</div>}
<AbstractList
items={annotations}
renderItem={this.renderItem}
getItemKey={item => {
return item.id + '';
}}
className="dashlist"
/>
</div>
);
}
}
# Annotation List Panel - Native Plugin
This Annotations List panel is **included** with Grafana.
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100px" height="100px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<g>
<g>
<path style="fill:#666666;" d="M8.842,11.219h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,11.219z"/>
<path style="fill:#666666;" d="M0.008,2.113l2.054-2.054C0.966,0.139,0.089,1.016,0.008,2.113z"/>
<polygon style="fill:#666666;" points="0,2.998 0,5.533 5.484,0.05 2.948,0.05 "/>
<polygon style="fill:#666666;" points="6.361,0.05 0,6.411 0,8.946 8.896,0.05 "/>
<path style="fill:#666666;" d="M11.169,2.277c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V2.277z"/>
<path style="fill:#666666;" d="M9.654,0.169L0.119,9.704c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,0.812,10.247,0.37,9.654,0.169z"/>
<polygon style="fill:#666666;" points="11.169,5.479 5.429,11.219 7.964,11.219 11.169,8.014 "/>
</g>
<path style="fill:#898989;" d="M88.146,11.031H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,10.212,89.157,11.031,88.146,11.031z"/>
<g>
<path style="fill:#666666;" d="M8.842,23.902h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,23.902z"/>
<path style="fill:#666666;" d="M0.008,14.796l2.054-2.054C0.966,12.822,0.089,13.699,0.008,14.796z"/>
<polygon style="fill:#666666;" points="0,15.681 0,18.216 5.484,12.733 2.948,12.733 "/>
<polygon style="fill:#666666;" points="6.361,12.733 0,19.094 0,21.629 8.896,12.733 "/>
<path style="fill:#666666;" d="M11.169,14.96c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V14.96z"/>
<path style="fill:#666666;" d="M9.654,12.852l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,13.495,10.247,13.053,9.654,12.852z"/>
<polygon style="fill:#666666;" points="11.169,18.162 5.429,23.902 7.964,23.902 11.169,20.697 "/>
</g>
<path style="fill:#898989;" d="M88.146,23.714H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.83,1.83-1.83h73.281
c1.011,0,1.83,0.82,1.83,1.83v7.37C89.977,22.895,89.157,23.714,88.146,23.714z"/>
<g>
<path style="fill:#666666;" d="M8.842,36.585h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,36.585z"/>
<path style="fill:#666666;" d="M0.008,27.479l2.054-2.054C0.966,25.505,0.089,26.382,0.008,27.479z"/>
<polygon style="fill:#666666;" points="0,28.364 0,30.899 5.484,25.416 2.948,25.416 "/>
<polygon style="fill:#666666;" points="6.361,25.416 0,31.777 0,34.312 8.896,25.416 "/>
<path style="fill:#666666;" d="M11.169,27.643c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V27.643z"/>
<path style="fill:#666666;" d="M9.654,25.535L0.119,35.07c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,26.178,10.247,25.736,9.654,25.535z"/>
<polygon style="fill:#666666;" points="11.169,30.845 5.429,36.585 7.964,36.585 11.169,33.38 "/>
</g>
<path style="fill:#898989;" d="M88.146,36.397H14.866c-1.011,0-1.83-0.82-1.83-1.831v-7.37c0-1.011,0.82-1.83,1.83-1.83h73.281
c1.011,0,1.83,0.82,1.83,1.83v7.37C89.977,35.578,89.157,36.397,88.146,36.397z"/>
<g>
<path style="fill:#666666;" d="M8.842,49.268h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,49.268z"/>
<path style="fill:#666666;" d="M0.008,40.162l2.054-2.054C0.966,38.188,0.089,39.065,0.008,40.162z"/>
<polygon style="fill:#666666;" points="0,41.047 0,43.582 5.484,38.099 2.948,38.099 "/>
<polygon style="fill:#666666;" points="6.361,38.099 0,44.46 0,46.995 8.896,38.099 "/>
<path style="fill:#666666;" d="M11.169,40.326c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V40.326z"/>
<path style="fill:#666666;" d="M9.654,38.218l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,38.861,10.247,38.419,9.654,38.218z"/>
<polygon style="fill:#666666;" points="11.169,43.528 5.429,49.268 7.964,49.268 11.169,46.063 "/>
</g>
<path style="fill:#898989;" d="M88.146,49.08H14.866c-1.011,0-1.83-0.82-1.83-1.831v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,48.261,89.157,49.08,88.146,49.08z"/>
<g>
<path style="fill:#666666;" d="M8.842,61.951h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,61.951z"/>
<path style="fill:#666666;" d="M0.008,52.845l2.054-2.054C0.966,50.871,0.089,51.748,0.008,52.845z"/>
<polygon style="fill:#666666;" points="0,53.73 0,56.265 5.484,50.782 2.948,50.782 "/>
<polygon style="fill:#666666;" points="6.361,50.782 0,57.143 0,59.678 8.896,50.782 "/>
<path style="fill:#666666;" d="M11.169,53.009c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V53.009z"/>
<path style="fill:#666666;" d="M9.654,50.901l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,51.544,10.247,51.102,9.654,50.901z"/>
<polygon style="fill:#666666;" points="11.169,56.211 5.429,61.951 7.964,61.951 11.169,58.746 "/>
</g>
<path style="fill:#898989;" d="M88.146,61.763H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,60.944,89.157,61.763,88.146,61.763z"/>
<g>
<path style="fill:#666666;" d="M8.842,74.634h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,74.634z"/>
<path style="fill:#666666;" d="M0.008,65.528l2.054-2.054C0.966,63.554,0.089,64.431,0.008,65.528z"/>
<polygon style="fill:#666666;" points="0,66.413 0,68.948 5.484,63.465 2.948,63.465 "/>
<polygon style="fill:#666666;" points="6.361,63.465 0,69.826 0,72.361 8.896,63.465 "/>
<path style="fill:#666666;" d="M11.169,65.692c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V65.692z"/>
<path style="fill:#666666;" d="M9.654,63.584l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,64.227,10.247,63.785,9.654,63.584z"/>
<polygon style="fill:#666666;" points="11.169,68.894 5.429,74.634 7.964,74.634 11.169,71.429 "/>
</g>
<path style="fill:#898989;" d="M88.146,74.446H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,73.627,89.157,74.446,88.146,74.446z"/>
<g>
<path style="fill:#666666;" d="M8.842,87.317h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,87.317z"/>
<path style="fill:#666666;" d="M0.008,78.211l2.054-2.054C0.966,76.237,0.089,77.114,0.008,78.211z"/>
<polygon style="fill:#666666;" points="0,79.096 0,81.631 5.484,76.148 2.948,76.148 "/>
<polygon style="fill:#666666;" points="6.361,76.148 0,82.509 0,85.044 8.896,76.148 "/>
<path style="fill:#666666;" d="M11.169,78.375c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V78.375z"/>
<path style="fill:#666666;" d="M9.654,76.267l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,76.91,10.247,76.468,9.654,76.267z"/>
<polygon style="fill:#666666;" points="11.169,81.577 5.429,87.317 7.964,87.317 11.169,84.112 "/>
</g>
<path style="fill:#898989;" d="M88.146,87.129H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,86.31,89.157,87.129,88.146,87.129z"/>
<g>
<path style="fill:#666666;" d="M8.842,100h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,100z"/>
<path style="fill:#666666;" d="M0.008,90.894l2.054-2.054C0.966,88.92,0.089,89.797,0.008,90.894z"/>
<polygon style="fill:#666666;" points="0,91.779 0,94.314 5.484,88.831 2.948,88.831 "/>
<polygon style="fill:#666666;" points="6.361,88.831 0,95.192 0,97.727 8.896,88.831 "/>
<path style="fill:#666666;" d="M11.169,91.058c0-0.068-0.004-0.134-0.01-0.2L2.027,99.99c0.066,0.006,0.133,0.01,0.2,0.01h2.325
l6.617-6.617V91.058z"/>
<path style="fill:#666666;" d="M9.654,88.95l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
C10.728,89.593,10.247,89.151,9.654,88.95z"/>
<polygon style="fill:#666666;" points="11.169,94.26 5.429,100 7.964,100 11.169,96.795 "/>
</g>
<path style="fill:#898989;" d="M88.146,99.812H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.83,1.83-1.83h73.281
c1.011,0,1.83,0.82,1.83,1.83v7.37C89.977,98.993,89.157,99.812,88.146,99.812z"/>
<circle style="fill:#F7941E;" cx="96.125" cy="5.637" r="3.875"/>
<circle style="fill:#898989;" cx="96.125" cy="18.37" r="3.875"/>
<circle style="fill:#898989;" cx="96.125" cy="31.104" r="3.875"/>
<circle style="fill:#F7941E;" cx="96.125" cy="43.837" r="3.875"/>
<circle style="fill:#F7941E;" cx="96.125" cy="56.57" r="3.875"/>
<circle style="fill:#898989;" cx="96.125" cy="69.304" r="3.875"/>
<circle style="fill:#F7941E;" cx="96.125" cy="82.037" r="3.875"/>
<circle style="fill:#898989;" cx="96.125" cy="94.77" r="3.875"/>
</g>
</svg>
import { AnnoListPanel } from './AnnoListPanel';
import { AnnoOptions, defaults } from './types';
import { AnnoListEditor } from './AnnoListEditor';
import { PanelPlugin } from '@grafana/ui';
export const plugin = new PanelPlugin<AnnoOptions>(AnnoListPanel)
.setDefaults(defaults)
.setEditor(AnnoListEditor)
// TODO, we should support this directly in the plugin infrastructure
.setPanelChangeHandler((options: AnnoOptions, prevPluginId: string, prevOptions: any) => {
if (prevPluginId === 'ryantxu-annolist-panel') {
return prevOptions as AnnoOptions;
}
return options;
});
{
"type": "panel",
"name": "Annotations list (alpha)",
"id": "annolist",
"state": "alpha",
"skipDataQuery": true,
"info": {
"description": "List annotations",
"author": {
"name": "Grafana Project",
"url": "https://grafana.com"
},
"logos": {
"small": "img/icn-annolist-panel.svg",
"large": "img/icn-annolist-panel.svg"
}
}
}
export interface AnnoOptions {
limit: number;
tags: string[];
onlyFromThisDashboard: boolean;
onlyInTimeRange: boolean;
showTags: boolean;
showUser: boolean;
showTime: boolean;
navigateBefore: string;
navigateAfter: string;
navigateToPanel: boolean;
}
export const defaults: AnnoOptions = {
limit: 10,
tags: [],
onlyFromThisDashboard: false,
onlyInTimeRange: false,
showTags: true,
showUser: true,
showTime: true,
navigateBefore: '10m',
navigateAfter: '10m',
navigateToPanel: true,
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment