Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VizPanel: Allow queries to be cancelled #220

Merged
merged 13 commits into from
Jun 19, 2023
2 changes: 2 additions & 0 deletions packages/scenes-app/src/demos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { getResponsiveLayoutDemo } from './responsiveLayout';
import { getVariablesDemo } from './variables';
import { getDrilldownsAppPageScene } from './withDrilldown/WithDrilldown';
import { getTimeZoneTest } from './timeZones';
import { getQueryCancellationTest } from './queryCancellation';

export interface DemoDescriptor {
title: string;
Expand All @@ -39,5 +40,6 @@ export function getDemos(): DemoDescriptor[] {
{ title: 'Runtime panel plugin', getPage: getRuntimePanelPluginDemo },
{ title: 'Behaviors demo', getPage: getBehaviorsDemo },
{ title: 'Time zones demo', getPage: getTimeZoneTest },
{ title: 'Query cancellation', getPage: getQueryCancellationTest },
];
}
93 changes: 93 additions & 0 deletions packages/scenes-app/src/demos/queryCancellation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import {
EmbeddedScene,
SceneAppPage,
SceneAppPageState,
SceneDataTransformer,
SceneFlexItem,
SceneFlexLayout,
VizPanel,
} from '@grafana/scenes';
import { getQueryRunnerWithRandomWalkQuery, getEmbeddedSceneDefaults } from './utils';

export function getQueryCancellationTest(defaults: SceneAppPageState) {
return new SceneAppPage({
...defaults,
subTitle: 'Demo of query cancellation',
getScene: () => {
return new EmbeddedScene({
...getEmbeddedSceneDefaults(),
key: 'Flex layout embedded scene',
$data: getQueryRunnerWithRandomWalkQuery(
{ scenarioId: 'slow_query', stringInput: '15s' },
{ maxDataPointsFromWidth: false }
),
body: new SceneFlexLayout({
direction: 'column',
children: [
new SceneFlexLayout({
direction: 'row',
children: [
new SceneFlexItem({
minWidth: '25%',
body: new VizPanel({
pluginId: 'timeseries',
title: 'Global query',
}),
}),
new SceneFlexItem({
minWidth: '25%',
body: new VizPanel({
$data: getQueryRunnerWithRandomWalkQuery(
{ scenarioId: 'slow_query', stringInput: '15s' },
{ maxDataPointsFromWidth: false }
),
pluginId: 'timeseries',
title: 'Local query',
}),
}),
new SceneFlexItem({
minWidth: '25%',
body: new VizPanel({
pluginId: 'stat',
title: 'Local query via transformation',
$data: new SceneDataTransformer({
$data: getQueryRunnerWithRandomWalkQuery(
{ scenarioId: 'slow_query', stringInput: '15s' },
{ maxDataPointsFromWidth: true }
),
transformations: [
{
id: 'reduce',
options: {
reducers: ['mean'],
},
},
],
}),
}),
}),
new SceneFlexItem({
minWidth: '25%',
body: new VizPanel({
pluginId: 'stat',
title: 'Global query via transformation',
$data: new SceneDataTransformer({
transformations: [
{
id: 'reduce',
options: {
reducers: ['mean'],
},
},
],
}),
}),
}),
],
}),
],
}),
});
},
});
}
5 changes: 5 additions & 0 deletions packages/scenes/src/components/VizPanel/VizPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@ export class VizPanel<TOptions = {}, TFieldConfig = {}> extends SceneObjectBase<
return this._dataWithFieldConfig;
}

public onCancelQuery = () => {
const data = sceneGraph.getData(this);
data.cancelQuery?.();
}

/**
* Panel context functions
*/
Expand Down
4 changes: 4 additions & 0 deletions packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { sceneGraph } from '../../core/sceneGraph';
import { isSceneObject, SceneComponentProps } from '../../core/types';

import { VizPanel } from './VizPanel';
import { SceneQueryRunner } from '../../querying/SceneQueryRunner';
import { SceneDataTransformer } from '../../querying/SceneDataTransformer';

export function VizPanelRenderer({ model }: SceneComponentProps<VizPanel>) {
const {
Expand Down Expand Up @@ -86,6 +88,7 @@ export function VizPanelRenderer({ model }: SceneComponentProps<VizPanel>) {
// Data is always returned. For non-data panels, empty PanelData is returned.
const data = dataWithFieldConfig!;
const isReadyToRender = dataObject.isDataReadyToDisplay ? dataObject.isDataReadyToDisplay() : true;
const canCancel = $data instanceof SceneQueryRunner || ($data instanceof SceneDataTransformer && $data.state.$data instanceof SceneQueryRunner);
kaydelaney marked this conversation as resolved.
Show resolved Hide resolved

return (
<div ref={ref as RefCallback<HTMLDivElement>} style={{ position: 'absolute', width: '100%', height: '100%' }}>
Expand All @@ -105,6 +108,7 @@ export function VizPanelRenderer({ model }: SceneComponentProps<VizPanel>) {
dragClassCancel={dragClassCancel}
padding={plugin.noPadding ? 'none' : 'md'}
menu={panelMenu}
onCancelQuery={canCancel ? model.onCancelQuery : undefined}
>
{(innerWidth, innerHeight) => (
<>
Expand Down
1 change: 1 addition & 0 deletions packages/scenes/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export type DeepPartial<T> = {
export interface SceneDataProvider extends SceneObject<SceneDataState> {
setContainerWidth?: (width: number) => void;
isDataReadyToDisplay?: () => boolean;
cancelQuery?: () => void;
}

export type SceneStatelessBehavior = (sceneObject: SceneObject) => CancelActivationHandler | void;
7 changes: 7 additions & 0 deletions packages/scenes/src/querying/SceneDataTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { sceneGraph } from '../core/sceneGraph';
import { SceneObjectBase } from '../core/SceneObjectBase';
import { CustomTransformOperator, SceneDataProvider, SceneDataState } from '../core/types';
import { VariableDependencyConfig } from '../variables/VariableDependencyConfig';
import { SceneQueryRunner } from './SceneQueryRunner';

export interface SceneDataTransformerState extends SceneDataState {
/**
Expand Down Expand Up @@ -87,6 +88,12 @@ export class SceneDataTransformer extends SceneObjectBase<SceneDataTransformerSt
this.transform(this.getSourceData().state.data);
}

public cancelQuery() {
if (this.state.$data instanceof SceneQueryRunner) {
this.getSourceData().cancelQuery?.();
}
dprokop marked this conversation as resolved.
Show resolved Hide resolved
dprokop marked this conversation as resolved.
Show resolved Hide resolved
}

private transform(data: PanelData | undefined) {
const transformations = this.state.transformations || [];

Expand Down
9 changes: 8 additions & 1 deletion packages/scenes/src/querying/SceneQueryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,13 @@ export class SceneQueryRunner extends SceneObjectBase<QueryRunnerState> implemen
return this.state.maxDataPoints ?? this._containerWidth ?? 500;
}

public cancelQuery() {
this._querySub?.unsubscribe();
this.setState({
data: { ...this.state.data!, state: LoadingState.Done },
});
}

private async runWithTimeRange(timeRange: SceneTimeRangeLike) {
// Skip executing queries if variable dependency is in loading state
if (sceneGraph.hasVariableDependencyInLoadingState(this)) {
Expand All @@ -161,7 +168,7 @@ export class SceneQueryRunner extends SceneObjectBase<QueryRunnerState> implemen
return;
}

// If we where waiting for variables clear that flag
// If we were waiting for variables, clear that flag
if (this.state.isWaitingForVariables) {
this.setState({ isWaitingForVariables: false });
}
Expand Down