mirror of
https://github.com/grafana/grafana.git
synced 2025-02-20 11:48:34 -06:00
* Annotations: Makes various annotation and alert calls cancelable Fixes #21943 * Refactor: Uses backendSrv.get method with requestId instead * Update public/app/features/annotations/annotations_srv.ts Co-Authored-By: Marcus Efraimsson <marcus.efraimsson@gmail.com> Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
305 lines
8.1 KiB
TypeScript
305 lines
8.1 KiB
TypeScript
// Libraries
|
|
import React, { PureComponent } from 'react';
|
|
// Types
|
|
import { AnnoOptions } from './types';
|
|
import { AnnotationEvent, AppEvents, dateTime, DurationUnit, PanelProps } from '@grafana/data';
|
|
import { Tooltip } from '@grafana/ui';
|
|
import { getBackendSrv } from '@grafana/runtime';
|
|
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 { css, cx } 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, `anno-list-panel-${this.props.id}`);
|
|
|
|
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(AppEvents.alertWarning, ['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: </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>
|
|
);
|
|
}
|
|
}
|