mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PanelHeader: show streaming indicator (and allow unsubscribe) (#28682)
This commit is contained in:
@@ -13,7 +13,7 @@ export interface PopoverContentProps {
|
|||||||
|
|
||||||
export type PopoverContent = string | React.ReactElement<any> | ((props: PopoverContentProps) => JSX.Element);
|
export type PopoverContent = string | React.ReactElement<any> | ((props: PopoverContentProps) => JSX.Element);
|
||||||
|
|
||||||
export const Tooltip: FC<TooltipProps> = ({ children, theme, ...controllerProps }: TooltipProps) => {
|
export const Tooltip: FC<TooltipProps> = React.memo(({ children, theme, ...controllerProps }: TooltipProps) => {
|
||||||
const tooltipTriggerRef = createRef<PopperJS.ReferenceObject>();
|
const tooltipTriggerRef = createRef<PopperJS.ReferenceObject>();
|
||||||
const popperBackgroundClassName = 'popper__background' + (theme ? ' popper__background--' + theme : '');
|
const popperBackgroundClassName = 'popper__background' + (theme ? ' popper__background--' + theme : '');
|
||||||
|
|
||||||
@@ -52,4 +52,4 @@ export const Tooltip: FC<TooltipProps> = ({ children, theme, ...controllerProps
|
|||||||
}}
|
}}
|
||||||
</PopoverController>
|
</PopoverController>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { DataLink, LoadingState, PanelData, PanelMenuItem, QueryResultMetaNotice, ScopedVars } from '@grafana/data';
|
import { DataLink, LoadingState, PanelData, PanelMenuItem, QueryResultMetaNotice, ScopedVars } from '@grafana/data';
|
||||||
import { AngularComponent, getTemplateSrv } from '@grafana/runtime';
|
import { AngularComponent, config, getTemplateSrv } from '@grafana/runtime';
|
||||||
import { ClickOutsideWrapper, Icon, IconName, Tooltip } from '@grafana/ui';
|
import { ClickOutsideWrapper, Icon, IconName, Tooltip, stylesFactory } from '@grafana/ui';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
|
||||||
import PanelHeaderCorner from './PanelHeaderCorner';
|
import PanelHeaderCorner from './PanelHeaderCorner';
|
||||||
@@ -14,6 +14,7 @@ import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
|||||||
import { getPanelLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
|
import { getPanelLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
|
||||||
import { getPanelMenu } from 'app/features/dashboard/utils/getPanelMenu';
|
import { getPanelMenu } from 'app/features/dashboard/utils/getPanelMenu';
|
||||||
import { updateLocation } from 'app/core/actions';
|
import { updateLocation } from 'app/core/actions';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
panel: PanelModel;
|
panel: PanelModel;
|
||||||
@@ -41,7 +42,7 @@ interface State {
|
|||||||
menuItems: PanelMenuItem[];
|
menuItems: PanelMenuItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PanelHeader extends Component<Props, State> {
|
export class PanelHeader extends PureComponent<Props, State> {
|
||||||
clickCoordinates: ClickCoordinates = { x: 0, y: 0 };
|
clickCoordinates: ClickCoordinates = { x: 0, y: 0 };
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
@@ -90,14 +91,28 @@ export class PanelHeader extends Component<Props, State> {
|
|||||||
this.props.panel.getQueryRunner().cancelQuery();
|
this.props.panel.getQueryRunner().cancelQuery();
|
||||||
};
|
};
|
||||||
|
|
||||||
private renderLoadingState(): JSX.Element {
|
renderLoadingState(state: LoadingState): JSX.Element | null {
|
||||||
return (
|
if (state === LoadingState.Loading) {
|
||||||
<div className="panel-loading" onClick={this.onCancelQuery}>
|
return (
|
||||||
<Tooltip content="Cancel query">
|
<div className="panel-loading" onClick={this.onCancelQuery}>
|
||||||
<Icon className="panel-loading__spinner spin-clockwise" name="sync" />
|
<Tooltip content="Cancel query">
|
||||||
</Tooltip>
|
<Icon className="panel-loading__spinner spin-clockwise" name="sync" />
|
||||||
</div>
|
</Tooltip>
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === LoadingState.Streaming) {
|
||||||
|
const styles = getStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="panel-loading" onClick={this.onCancelQuery}>
|
||||||
|
<div title="Streaming (click to stop)" className={styles.streamIndicator} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
openInspect = (e: React.SyntheticEvent, tab: string) => {
|
openInspect = (e: React.SyntheticEvent, tab: string) => {
|
||||||
@@ -156,7 +171,7 @@ export class PanelHeader extends Component<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{data.state === LoadingState.Loading && this.renderLoadingState()}
|
{this.renderLoadingState(data.state)}
|
||||||
<div className={panelHeaderClass}>
|
<div className={panelHeaderClass}>
|
||||||
<PanelHeaderCorner
|
<PanelHeaderCorner
|
||||||
panel={panel}
|
panel={panel}
|
||||||
@@ -201,3 +216,21 @@ export class PanelHeader extends Component<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Styles
|
||||||
|
*/
|
||||||
|
export const getStyles = stylesFactory(() => {
|
||||||
|
return {
|
||||||
|
streamIndicator: css`
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background: ${config.theme.colors.textFaint};
|
||||||
|
box-shadow: 0 0 2px ${config.theme.colors.textFaint};
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
top: 6px;
|
||||||
|
right: 1px;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ exports[`Render should render component 1`] = `
|
|||||||
>
|
>
|
||||||
External group sync
|
External group sync
|
||||||
</h3>
|
</h3>
|
||||||
<Component
|
<Memo()
|
||||||
content="Sync LDAP or OAuth groups with your Grafana teams."
|
content="Sync LDAP or OAuth groups with your Grafana teams."
|
||||||
placement="auto"
|
placement="auto"
|
||||||
>
|
>
|
||||||
@@ -18,7 +18,7 @@ exports[`Render should render component 1`] = `
|
|||||||
className="icon--has-hover page-sub-heading-icon"
|
className="icon--has-hover page-sub-heading-icon"
|
||||||
name="question-circle"
|
name="question-circle"
|
||||||
/>
|
/>
|
||||||
</Component>
|
</Memo()>
|
||||||
<div
|
<div
|
||||||
className="page-action-bar__spacer"
|
className="page-action-bar__spacer"
|
||||||
/>
|
/>
|
||||||
@@ -92,7 +92,7 @@ exports[`Render should render groups table 1`] = `
|
|||||||
>
|
>
|
||||||
External group sync
|
External group sync
|
||||||
</h3>
|
</h3>
|
||||||
<Component
|
<Memo()
|
||||||
content="Sync LDAP or OAuth groups with your Grafana teams."
|
content="Sync LDAP or OAuth groups with your Grafana teams."
|
||||||
placement="auto"
|
placement="auto"
|
||||||
>
|
>
|
||||||
@@ -100,7 +100,7 @@ exports[`Render should render groups table 1`] = `
|
|||||||
className="icon--has-hover page-sub-heading-icon"
|
className="icon--has-hover page-sub-heading-icon"
|
||||||
name="question-circle"
|
name="question-circle"
|
||||||
/>
|
/>
|
||||||
</Component>
|
</Memo()>
|
||||||
<div
|
<div
|
||||||
className="page-action-bar__spacer"
|
className="page-action-bar__spacer"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ export function runSignalStream(
|
|||||||
subscriber.next({
|
subscriber.next({
|
||||||
data: [data],
|
data: [data],
|
||||||
key: streamId,
|
key: streamId,
|
||||||
|
state: LoadingState.Streaming,
|
||||||
});
|
});
|
||||||
|
|
||||||
timeoutId = setTimeout(pushNextEvent, speed);
|
timeoutId = setTimeout(pushNextEvent, speed);
|
||||||
|
|||||||
Reference in New Issue
Block a user