| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- // 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: </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>
- );
- }
- }
|