// 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 {} interface State { annotations: AnnotationEvent[]; timeInfo: string; loaded: boolean; queryUser?: UserInfo; queryTags: string[]; } export class AnnoListPanel extends PureComponent { 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 ( this.onTagClick(e, tag, remove)} className="pointer"> ); })} ); }; renderItem = (anno: AnnotationEvent, index: number): JSX.Element => { const { options } = this.props; const { showUser, showTags, showTime } = options; const dashboard = getDashboardSrv().getCurrent(); return (
{ this.onAnnoClick(e, anno); }} > {anno.text} {anno.login && showUser && ( Created by:
{anno.email}
} theme="info" placement="top" > this.onUserClick(e, anno)} className="graph-annotation__user">
)} {showTags && this.renderTags(anno.tags, false)}
{showTime && {dashboard.formatDate(anno.time)}}
); }; render() { const { height } = this.props; const { loaded, annotations, queryUser, queryTags } = this.state; if (!loaded) { return
loading...
; } // Previously we showed inidication that it covered all time // { timeInfo && ( // // {timeInfo} // // )} const hasFilter = queryUser || queryTags.length > 0; return (
{hasFilter && (
Filter:   {queryUser && ( {queryUser.email} )} {queryTags.length > 0 && this.renderTags(queryTags, true)}
)} {annotations.length < 1 &&
No Annotations Found
} { return item.id + ''; }} className="dashlist" />
); } }