mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
VizPanel: Support panel migrations and state changes (#58501)
* VizPanel: Make it more real * Updates * Progress on query runner and max data points from width * Updated * Update * Tests * Fixed issue with migration * Moving VizPanel * Fixed migration issue due to pluginVersion not being set * Update public/app/features/scenes/querying/SceneQueryRunner.test.ts Co-authored-by: Ivan Ortega Alba <ivanortegaalba@gmail.com> * Some minor review fixes * Added expect Co-authored-by: Ivan Ortega Alba <ivanortegaalba@gmail.com>
This commit is contained in:
parent
15561b83e4
commit
156ed4b56c
@ -4554,6 +4554,9 @@ exports[`better eslint`] = {
|
||||
"public/app/features/sandbox/TestStuffPage.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/features/scenes/components/VizPanel/VizPanelRenderer.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/features/scenes/components/layout/SceneFlexLayout.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
|
@ -38,7 +38,7 @@ export function PanelRenderer<P extends object = any, F extends object = any>(pr
|
||||
const [plugin, setPlugin] = useState(syncGetPanelPlugin(pluginId));
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
const optionsWithDefaults = useOptionDefaults(plugin, options, fieldConfig);
|
||||
const dataWithOverrides = useFieldOverrides(plugin, optionsWithDefaults, data, timeZone);
|
||||
const dataWithOverrides = useFieldOverrides(plugin, optionsWithDefaults?.fieldConfig, data, timeZone);
|
||||
|
||||
useEffect(() => {
|
||||
// If we already have a plugin and it's correct one do nothing
|
||||
@ -117,13 +117,12 @@ function useOptionDefaults<P extends object = any, F extends object = any>(
|
||||
}, [plugin, fieldConfig, options]);
|
||||
}
|
||||
|
||||
function useFieldOverrides(
|
||||
export function useFieldOverrides(
|
||||
plugin: PanelPlugin | undefined,
|
||||
defaultOptions: OptionDefaults | undefined,
|
||||
fieldConfig: FieldConfigSource | undefined,
|
||||
data: PanelData | undefined,
|
||||
timeZone: string
|
||||
): PanelData | undefined {
|
||||
const fieldConfig = defaultOptions?.fieldConfig;
|
||||
const fieldConfigRegistry = plugin?.fieldConfigRegistry;
|
||||
const theme = useTheme2();
|
||||
const structureRev = useRef(0);
|
||||
|
@ -63,7 +63,7 @@ export function getPanelPlugin(
|
||||
version: '',
|
||||
},
|
||||
hideFromList: options.hideFromList === true,
|
||||
module: '',
|
||||
module: options.module ?? '',
|
||||
baseUrl: '',
|
||||
};
|
||||
return plugin;
|
||||
|
@ -1,99 +0,0 @@
|
||||
import React from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
|
||||
import { AbsoluteTimeRange, FieldConfigSource, toUtc } from '@grafana/data';
|
||||
import { PanelRenderer } from '@grafana/runtime';
|
||||
import { Field, PanelChrome, Input } from '@grafana/ui';
|
||||
|
||||
import { SceneObjectBase } from '../core/SceneObjectBase';
|
||||
import { sceneGraph } from '../core/sceneGraph';
|
||||
import { SceneComponentProps, SceneLayoutChildState } from '../core/types';
|
||||
import { VariableDependencyConfig } from '../variables/VariableDependencyConfig';
|
||||
|
||||
import { SceneDragHandle } from './SceneDragHandle';
|
||||
|
||||
export interface VizPanelState extends SceneLayoutChildState {
|
||||
title?: string;
|
||||
pluginId: string;
|
||||
options?: object;
|
||||
fieldConfig?: FieldConfigSource;
|
||||
}
|
||||
|
||||
export class VizPanel extends SceneObjectBase<VizPanelState> {
|
||||
public static Component = ScenePanelRenderer;
|
||||
public static Editor = VizPanelEditor;
|
||||
|
||||
protected _variableDependency = new VariableDependencyConfig(this, {
|
||||
statePaths: ['title'],
|
||||
});
|
||||
|
||||
public onSetTimeRange = (timeRange: AbsoluteTimeRange) => {
|
||||
const sceneTimeRange = sceneGraph.getTimeRange(this);
|
||||
sceneTimeRange.setState({
|
||||
raw: {
|
||||
from: toUtc(timeRange.from),
|
||||
to: toUtc(timeRange.to),
|
||||
},
|
||||
from: toUtc(timeRange.from),
|
||||
to: toUtc(timeRange.to),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function ScenePanelRenderer({ model }: SceneComponentProps<VizPanel>) {
|
||||
const { title, pluginId, options, fieldConfig, ...state } = model.useState();
|
||||
const { data } = sceneGraph.getData(model).useState();
|
||||
|
||||
const layout = sceneGraph.getLayout(model);
|
||||
const isDraggable = layout.state.isDraggable ? state.isDraggable : false;
|
||||
const dragHandle = <SceneDragHandle layoutKey={layout.state.key!} />;
|
||||
|
||||
const titleInterpolated = sceneGraph.interpolate(model, title);
|
||||
|
||||
return (
|
||||
<AutoSizer>
|
||||
{({ width, height }) => {
|
||||
if (width < 3 || height < 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<PanelChrome
|
||||
title={titleInterpolated}
|
||||
width={width}
|
||||
height={height}
|
||||
leftItems={isDraggable ? [dragHandle] : undefined}
|
||||
>
|
||||
{(innerWidth, innerHeight) => (
|
||||
<>
|
||||
<PanelRenderer
|
||||
title="Raw data"
|
||||
pluginId={pluginId}
|
||||
width={innerWidth}
|
||||
height={innerHeight}
|
||||
data={data}
|
||||
options={options}
|
||||
fieldConfig={fieldConfig}
|
||||
onOptionsChange={() => {}}
|
||||
onChangeTimeRange={model.onSetTimeRange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</PanelChrome>
|
||||
);
|
||||
}}
|
||||
</AutoSizer>
|
||||
);
|
||||
}
|
||||
|
||||
ScenePanelRenderer.displayName = 'ScenePanelRenderer';
|
||||
|
||||
function VizPanelEditor({ model }: SceneComponentProps<VizPanel>) {
|
||||
const { title } = model.useState();
|
||||
|
||||
return (
|
||||
<Field label="Title">
|
||||
<Input defaultValue={title} onBlur={(evt) => model.setState({ title: evt.currentTarget.value })} />
|
||||
</Field>
|
||||
);
|
||||
}
|
136
public/app/features/scenes/components/VizPanel/VizPanel.test.tsx
Normal file
136
public/app/features/scenes/components/VizPanel/VizPanel.test.tsx
Normal file
@ -0,0 +1,136 @@
|
||||
import React from 'react';
|
||||
|
||||
import { FieldConfigProperty, PanelPlugin } from '@grafana/data';
|
||||
import { getPanelPlugin } from 'app/features/plugins/__mocks__/pluginMocks';
|
||||
|
||||
import { VizPanel } from './VizPanel';
|
||||
|
||||
let pluginToLoad: PanelPlugin | undefined;
|
||||
|
||||
jest.mock('app/features/plugins/importPanelPlugin', () => ({
|
||||
syncGetPanelPlugin: jest.fn(() => pluginToLoad),
|
||||
}));
|
||||
|
||||
interface OptionsPlugin1 {
|
||||
showThresholds: boolean;
|
||||
option2?: string;
|
||||
}
|
||||
|
||||
interface FieldConfigPlugin1 {
|
||||
customProp?: boolean;
|
||||
customProp2?: boolean;
|
||||
junkProp?: boolean;
|
||||
}
|
||||
|
||||
function getTestPlugin1() {
|
||||
const pluginToLoad = getPanelPlugin(
|
||||
{
|
||||
id: 'custom-plugin-id',
|
||||
},
|
||||
() => <div>My custom panel</div>
|
||||
);
|
||||
|
||||
pluginToLoad.meta.info.version = '1.0.0';
|
||||
pluginToLoad.setPanelOptions((builder) => {
|
||||
builder.addBooleanSwitch({
|
||||
name: 'Show thresholds',
|
||||
path: 'showThresholds',
|
||||
defaultValue: true,
|
||||
});
|
||||
builder.addTextInput({
|
||||
name: 'option2',
|
||||
path: 'option2',
|
||||
defaultValue: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
pluginToLoad.useFieldConfig({
|
||||
standardOptions: {
|
||||
[FieldConfigProperty.Unit]: {
|
||||
defaultValue: 'flop',
|
||||
},
|
||||
[FieldConfigProperty.Decimals]: {
|
||||
defaultValue: 2,
|
||||
},
|
||||
},
|
||||
useCustomConfig: (builder) => {
|
||||
builder.addBooleanSwitch({
|
||||
name: 'CustomProp',
|
||||
path: 'customProp',
|
||||
defaultValue: false,
|
||||
});
|
||||
builder.addBooleanSwitch({
|
||||
name: 'customProp2',
|
||||
path: 'customProp2',
|
||||
defaultValue: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
pluginToLoad.setMigrationHandler((panel) => {
|
||||
if (panel.fieldConfig.defaults.custom) {
|
||||
panel.fieldConfig.defaults.custom.customProp2 = true;
|
||||
}
|
||||
|
||||
return { option2: 'hello' };
|
||||
});
|
||||
|
||||
return pluginToLoad;
|
||||
}
|
||||
|
||||
describe('VizPanel', () => {
|
||||
describe('when activated', () => {
|
||||
let panel: VizPanel<OptionsPlugin1, FieldConfigPlugin1>;
|
||||
|
||||
beforeAll(async () => {
|
||||
panel = new VizPanel<OptionsPlugin1, FieldConfigPlugin1>({
|
||||
pluginId: 'custom-plugin-id',
|
||||
fieldConfig: {
|
||||
defaults: { custom: { junkProp: true } },
|
||||
overrides: [],
|
||||
},
|
||||
});
|
||||
|
||||
pluginToLoad = getTestPlugin1();
|
||||
panel.activate();
|
||||
});
|
||||
|
||||
it('load plugin', () => {
|
||||
expect(panel.getPlugin()).toBe(pluginToLoad);
|
||||
});
|
||||
|
||||
it('should call panel migration handler', () => {
|
||||
expect(panel.state.options.option2).toEqual('hello');
|
||||
expect(panel.state.fieldConfig.defaults.custom?.customProp2).toEqual(true);
|
||||
});
|
||||
|
||||
it('should apply option defaults', () => {
|
||||
expect(panel.state.options.showThresholds).toEqual(true);
|
||||
});
|
||||
|
||||
it('should apply fieldConfig defaults', () => {
|
||||
expect(panel.state.fieldConfig.defaults.unit).toBe('flop');
|
||||
expect(panel.state.fieldConfig.defaults.custom!.customProp).toBe(false);
|
||||
});
|
||||
|
||||
it('should should remove props that are not defined for plugin', () => {
|
||||
expect(panel.state.fieldConfig.defaults.custom?.junkProp).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When calling on onPanelMigration', () => {
|
||||
const onPanelMigration = jest.fn();
|
||||
let panel: VizPanel<OptionsPlugin1, FieldConfigPlugin1>;
|
||||
|
||||
beforeAll(async () => {
|
||||
panel = new VizPanel<OptionsPlugin1, FieldConfigPlugin1>({ pluginId: 'custom-plugin-id' });
|
||||
pluginToLoad = getTestPlugin1();
|
||||
pluginToLoad.onPanelMigration = onPanelMigration;
|
||||
panel.activate();
|
||||
});
|
||||
|
||||
it('should call onPanelMigration with pluginVersion set to initial state (undefined)', () => {
|
||||
expect(onPanelMigration.mock.calls[0][0].pluginVersion).toBe(undefined);
|
||||
});
|
||||
});
|
||||
});
|
125
public/app/features/scenes/components/VizPanel/VizPanel.tsx
Normal file
125
public/app/features/scenes/components/VizPanel/VizPanel.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
import React from 'react';
|
||||
|
||||
import { AbsoluteTimeRange, FieldConfigSource, PanelModel, PanelPlugin, toUtc } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Field, Input } from '@grafana/ui';
|
||||
import { importPanelPlugin, syncGetPanelPlugin } from 'app/features/plugins/importPanelPlugin';
|
||||
|
||||
import { getPanelOptionsWithDefaults } from '../../../dashboard/state/getPanelOptionsWithDefaults';
|
||||
import { SceneObjectBase } from '../../core/SceneObjectBase';
|
||||
import { sceneGraph } from '../../core/sceneGraph';
|
||||
import { SceneComponentProps, SceneLayoutChildState } from '../../core/types';
|
||||
|
||||
import { VizPanelRenderer } from './VizPanelRenderer';
|
||||
|
||||
export interface VizPanelState<TOptions = {}, TFieldConfig = {}> extends SceneLayoutChildState {
|
||||
title: string;
|
||||
pluginId: string;
|
||||
options: TOptions;
|
||||
fieldConfig: FieldConfigSource<TFieldConfig>;
|
||||
pluginVersion?: string;
|
||||
// internal state
|
||||
pluginLoadError?: string;
|
||||
}
|
||||
|
||||
export class VizPanel<TOptions = {}, TFieldConfig = {}> extends SceneObjectBase<
|
||||
VizPanelState<Partial<TOptions>, TFieldConfig>
|
||||
> {
|
||||
public static Component = VizPanelRenderer;
|
||||
public static Editor = VizPanelEditor;
|
||||
|
||||
// Not part of state as this is not serializable
|
||||
private _plugin?: PanelPlugin;
|
||||
|
||||
public constructor(state: Partial<VizPanelState<Partial<TOptions>, TFieldConfig>>) {
|
||||
super({
|
||||
options: {},
|
||||
fieldConfig: { defaults: {}, overrides: [] },
|
||||
title: 'Title',
|
||||
pluginId: 'timeseries',
|
||||
...state,
|
||||
});
|
||||
}
|
||||
|
||||
public activate() {
|
||||
super.activate();
|
||||
|
||||
const plugin = syncGetPanelPlugin(this.state.pluginId);
|
||||
|
||||
if (plugin) {
|
||||
this.pluginLoaded(plugin);
|
||||
} else {
|
||||
importPanelPlugin(this.state.pluginId)
|
||||
.then((result) => this.pluginLoaded(result))
|
||||
.catch((err: Error) => {
|
||||
this.setState({ pluginLoadError: err.message });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private pluginLoaded(plugin: PanelPlugin) {
|
||||
const { options, fieldConfig, title, pluginId, pluginVersion } = this.state;
|
||||
|
||||
const panel: PanelModel = { title, options, fieldConfig, id: 1, type: pluginId, pluginVersion: pluginVersion };
|
||||
const currentVersion = this.getPluginVersion(plugin);
|
||||
|
||||
if (plugin.onPanelMigration) {
|
||||
if (currentVersion !== this.state.pluginVersion) {
|
||||
// These migration handlers also mutate panel.fieldConfig to migrate fieldConfig
|
||||
panel.options = plugin.onPanelMigration(panel);
|
||||
}
|
||||
}
|
||||
|
||||
const withDefaults = getPanelOptionsWithDefaults({
|
||||
plugin,
|
||||
currentOptions: panel.options,
|
||||
currentFieldConfig: panel.fieldConfig,
|
||||
isAfterPluginChange: false,
|
||||
});
|
||||
|
||||
this._plugin = plugin;
|
||||
this.setState({
|
||||
options: withDefaults.options,
|
||||
fieldConfig: withDefaults.fieldConfig,
|
||||
pluginVersion: currentVersion,
|
||||
});
|
||||
}
|
||||
|
||||
private getPluginVersion(plugin: PanelPlugin): string {
|
||||
return plugin && plugin.meta.info.version ? plugin.meta.info.version : config.buildInfo.version;
|
||||
}
|
||||
|
||||
public getPlugin(): PanelPlugin | undefined {
|
||||
return this._plugin;
|
||||
}
|
||||
|
||||
public onChangeTimeRange = (timeRange: AbsoluteTimeRange) => {
|
||||
const sceneTimeRange = sceneGraph.getTimeRange(this);
|
||||
sceneTimeRange.setState({
|
||||
raw: {
|
||||
from: toUtc(timeRange.from),
|
||||
to: toUtc(timeRange.to),
|
||||
},
|
||||
from: toUtc(timeRange.from),
|
||||
to: toUtc(timeRange.to),
|
||||
});
|
||||
};
|
||||
|
||||
public onOptionsChange = (options: TOptions) => {
|
||||
this.setState({ options });
|
||||
};
|
||||
|
||||
public onFieldConfigChange = (fieldConfig: FieldConfigSource) => {
|
||||
this.setState({ fieldConfig });
|
||||
};
|
||||
}
|
||||
|
||||
function VizPanelEditor({ model }: SceneComponentProps<VizPanel>) {
|
||||
const { title } = model.useState();
|
||||
|
||||
return (
|
||||
<Field label="Title">
|
||||
<Input defaultValue={title} onBlur={(evt) => model.setState({ title: evt.currentTarget.value })} />
|
||||
</Field>
|
||||
);
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
import React, { RefCallback } from 'react';
|
||||
import { useMeasure } from 'react-use';
|
||||
|
||||
import { PluginContextProvider } from '@grafana/data';
|
||||
import { PanelChrome, ErrorBoundaryAlert } from '@grafana/ui';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import { useFieldOverrides } from 'app/features/panel/components/PanelRenderer';
|
||||
|
||||
import { sceneGraph } from '../../core/sceneGraph';
|
||||
import { SceneComponentProps } from '../../core/types';
|
||||
import { SceneQueryRunner } from '../../querying/SceneQueryRunner';
|
||||
import { SceneDragHandle } from '../SceneDragHandle';
|
||||
|
||||
import { VizPanel } from './VizPanel';
|
||||
|
||||
export function VizPanelRenderer({ model }: SceneComponentProps<VizPanel>) {
|
||||
const { title, options, fieldConfig, pluginId, pluginLoadError, $data, ...state } = model.useState();
|
||||
const [ref, { width, height }] = useMeasure();
|
||||
const plugin = model.getPlugin();
|
||||
const { data } = sceneGraph.getData(model).useState();
|
||||
const layout = sceneGraph.getLayout(model);
|
||||
|
||||
const isDraggable = layout.state.isDraggable ? state.isDraggable : false;
|
||||
const dragHandle = <SceneDragHandle layoutKey={layout.state.key!} />;
|
||||
|
||||
const titleInterpolated = sceneGraph.interpolate(model, title);
|
||||
|
||||
// Not sure we need to subscribe to this state
|
||||
const timeZone = sceneGraph.getTimeRange(model).state.timeZone;
|
||||
|
||||
const dataWithOverrides = useFieldOverrides(plugin, fieldConfig, data, timeZone);
|
||||
|
||||
if (pluginLoadError) {
|
||||
return <div>Failed to load plugin: {pluginLoadError}</div>;
|
||||
}
|
||||
|
||||
if (!plugin || !plugin.hasPluginId(pluginId)) {
|
||||
return <div>Loading plugin panel...</div>;
|
||||
}
|
||||
|
||||
if (!plugin.panel) {
|
||||
return <div>Panel plugin has no panel component</div>;
|
||||
}
|
||||
|
||||
const PanelComponent = plugin.panel;
|
||||
|
||||
// Query runner needs to with for auto maxDataPoints
|
||||
if ($data instanceof SceneQueryRunner) {
|
||||
$data.setContainerWidth(width);
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={ref as RefCallback<HTMLDivElement>} style={{ width: '100%', height: '100%' }}>
|
||||
<PanelChrome
|
||||
title={titleInterpolated}
|
||||
width={width}
|
||||
height={height}
|
||||
leftItems={isDraggable ? [dragHandle] : undefined}
|
||||
>
|
||||
{(innerWidth, innerHeight) => (
|
||||
<>
|
||||
{!dataWithOverrides && <div>No data...</div>}
|
||||
{dataWithOverrides && (
|
||||
<ErrorBoundaryAlert dependencies={[plugin, data]}>
|
||||
<PluginContextProvider meta={plugin.meta}>
|
||||
<PanelComponent
|
||||
id={1}
|
||||
data={dataWithOverrides}
|
||||
title={title}
|
||||
timeRange={dataWithOverrides.timeRange}
|
||||
timeZone={timeZone}
|
||||
options={options}
|
||||
fieldConfig={fieldConfig}
|
||||
transparent={false}
|
||||
width={innerWidth}
|
||||
height={innerHeight}
|
||||
renderCounter={0}
|
||||
replaceVariables={(str: string) => str}
|
||||
onOptionsChange={model.onOptionsChange}
|
||||
onFieldConfigChange={model.onFieldConfigChange}
|
||||
onChangeTimeRange={model.onChangeTimeRange}
|
||||
eventBus={appEvents}
|
||||
/>
|
||||
</PluginContextProvider>
|
||||
</ErrorBoundaryAlert>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PanelChrome>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
VizPanelRenderer.displayName = 'ScenePanelRenderer';
|
10
public/app/features/scenes/components/index.ts
Normal file
10
public/app/features/scenes/components/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export { VizPanel } from './VizPanel/VizPanel';
|
||||
export { NestedScene } from './NestedScene';
|
||||
export { Scene } from './Scene';
|
||||
export { SceneCanvasText } from './SceneCanvasText';
|
||||
export { SceneToolbarButton, SceneToolbarInput } from './SceneToolbarButton';
|
||||
export { SceneTimePicker } from './SceneTimePicker';
|
||||
export { ScenePanelRepeater } from './ScenePanelRepeater';
|
||||
export { SceneSubMenu } from './SceneSubMenu';
|
||||
export { SceneFlexLayout } from './layout/SceneFlexLayout';
|
||||
export { SceneGridLayout, SceneGridRow } from './layout/SceneGridLayout';
|
@ -1,9 +1,17 @@
|
||||
import { TimeRange, UrlQueryMap } from '@grafana/data';
|
||||
import { getDefaultTimeRange, getTimeZone, TimeRange, UrlQueryMap } from '@grafana/data';
|
||||
|
||||
import { SceneObjectBase } from './SceneObjectBase';
|
||||
import { SceneObjectWithUrlSync, SceneTimeRangeState } from './types';
|
||||
|
||||
export class SceneTimeRange extends SceneObjectBase<SceneTimeRangeState> implements SceneObjectWithUrlSync {
|
||||
public constructor(state: Partial<SceneTimeRangeState> = {}) {
|
||||
super({
|
||||
...getDefaultTimeRange(),
|
||||
timeZone: getTimeZone(),
|
||||
...state,
|
||||
});
|
||||
}
|
||||
|
||||
public onTimeRangeChange = (timeRange: TimeRange) => {
|
||||
this.setState(timeRange);
|
||||
};
|
||||
|
@ -108,7 +108,7 @@ export const EmptyDataNode = new SceneDataNode({
|
||||
},
|
||||
});
|
||||
|
||||
export const DefaultTimeRange = new SceneTimeRangeImpl(getDefaultTimeRange());
|
||||
export const DefaultTimeRange = new SceneTimeRangeImpl();
|
||||
|
||||
export const sceneGraph = {
|
||||
getVariables,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Observer, Subscription, Unsubscribable } from 'rxjs';
|
||||
|
||||
import { BusEvent, BusEventHandler, BusEventType, PanelData, TimeRange, UrlQueryMap } from '@grafana/data';
|
||||
import { BusEvent, BusEventHandler, BusEventType, PanelData, TimeRange, TimeZone, UrlQueryMap } from '@grafana/data';
|
||||
|
||||
import { SceneVariableDependencyConfigLike, SceneVariables } from '../variables/types';
|
||||
|
||||
@ -128,7 +128,9 @@ interface SceneComponentEditWrapperProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface SceneTimeRangeState extends SceneObjectStatePlain, TimeRange {}
|
||||
export interface SceneTimeRangeState extends SceneObjectStatePlain, TimeRange {
|
||||
timeZone: TimeZone;
|
||||
}
|
||||
|
||||
export interface SceneTimeRange extends SceneObject<SceneTimeRangeState> {
|
||||
onTimeRangeChange(timeRange: TimeRange): void;
|
||||
|
@ -1,12 +1,9 @@
|
||||
import { getDefaultTimeRange } from '@grafana/data';
|
||||
import { StateManagerBase } from 'app/core/services/StateManagerBase';
|
||||
import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import { DashboardDTO } from 'app/types';
|
||||
|
||||
import { SceneTimePicker } from '../components/SceneTimePicker';
|
||||
import { VizPanel } from '../components/VizPanel';
|
||||
import { SceneGridLayout, SceneGridRow } from '../components/layout/SceneGridLayout';
|
||||
import { VizPanel, SceneTimePicker, SceneGridLayout, SceneGridRow } from '../components';
|
||||
import { SceneTimeRange } from '../core/SceneTimeRange';
|
||||
import { SceneObject } from '../core/types';
|
||||
import { SceneQueryRunner } from '../querying/SceneQueryRunner';
|
||||
@ -54,7 +51,7 @@ export class DashboardLoader extends StateManagerBase<DashboardLoaderState> {
|
||||
layout: new SceneGridLayout({
|
||||
children: this.buildSceneObjectsFromDashboard(oldModel),
|
||||
}),
|
||||
$timeRange: new SceneTimeRange(getDefaultTimeRange()),
|
||||
$timeRange: new SceneTimeRange(),
|
||||
actions: [new SceneTimePicker({})],
|
||||
});
|
||||
|
||||
@ -83,26 +80,7 @@ export class DashboardLoader extends StateManagerBase<DashboardLoaderState> {
|
||||
size: {
|
||||
y: panel.gridPos.y,
|
||||
},
|
||||
children: panel.panels
|
||||
? panel.panels.map(
|
||||
(p) =>
|
||||
new VizPanel({
|
||||
title: p.title,
|
||||
pluginId: p.type,
|
||||
size: {
|
||||
x: p.gridPos.x,
|
||||
y: p.gridPos.y,
|
||||
width: p.gridPos.w,
|
||||
height: p.gridPos.h,
|
||||
},
|
||||
options: p.options,
|
||||
fieldConfig: p.fieldConfig,
|
||||
$data: new SceneQueryRunner({
|
||||
queries: p.targets,
|
||||
}),
|
||||
})
|
||||
)
|
||||
: [],
|
||||
children: panel.panels ? panel.panels.map(createVizPanelFromPanelModel) : [],
|
||||
})
|
||||
);
|
||||
} else {
|
||||
@ -128,21 +106,7 @@ export class DashboardLoader extends StateManagerBase<DashboardLoaderState> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const panelObject = new VizPanel({
|
||||
title: panel.title,
|
||||
pluginId: panel.type,
|
||||
size: {
|
||||
x: panel.gridPos.x,
|
||||
y: panel.gridPos.y,
|
||||
width: panel.gridPos.w,
|
||||
height: panel.gridPos.h,
|
||||
},
|
||||
options: panel.options,
|
||||
fieldConfig: panel.fieldConfig,
|
||||
$data: new SceneQueryRunner({
|
||||
queries: panel.targets,
|
||||
}),
|
||||
});
|
||||
const panelObject = createVizPanelFromPanelModel(panel);
|
||||
|
||||
// when processing an expanded row, collect its panels
|
||||
if (currentRow) {
|
||||
@ -170,6 +134,25 @@ export class DashboardLoader extends StateManagerBase<DashboardLoaderState> {
|
||||
}
|
||||
}
|
||||
|
||||
function createVizPanelFromPanelModel(panel: PanelModel) {
|
||||
return new VizPanel({
|
||||
title: panel.title,
|
||||
pluginId: panel.type,
|
||||
size: {
|
||||
x: panel.gridPos.x,
|
||||
y: panel.gridPos.y,
|
||||
width: panel.gridPos.w,
|
||||
height: panel.gridPos.h,
|
||||
},
|
||||
options: panel.options,
|
||||
fieldConfig: panel.fieldConfig,
|
||||
pluginVersion: panel.pluginVersion,
|
||||
$data: new SceneQueryRunner({
|
||||
queries: panel.targets,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
let loader: DashboardLoader | null = null;
|
||||
|
||||
export function getDashboardLoader(): DashboardLoader {
|
||||
|
83
public/app/features/scenes/querying/SceneQueryRunner.test.ts
Normal file
83
public/app/features/scenes/querying/SceneQueryRunner.test.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { DataQueryRequest, DataSourceApi, getDefaultTimeRange, LoadingState, PanelData } from '@grafana/data';
|
||||
|
||||
import { SceneTimeRange } from '../core/SceneTimeRange';
|
||||
|
||||
import { SceneQueryRunner } from './SceneQueryRunner';
|
||||
|
||||
const getDatasource = () => {
|
||||
return {
|
||||
getRef: () => ({ uid: 'test' }),
|
||||
};
|
||||
};
|
||||
|
||||
jest.mock('app/features/plugins/datasource_srv', () => ({
|
||||
getDatasourceSrv: jest.fn(() => ({
|
||||
get: getDatasource,
|
||||
})),
|
||||
}));
|
||||
|
||||
const runRequest = jest.fn().mockReturnValue(
|
||||
of<PanelData>({
|
||||
state: LoadingState.Done,
|
||||
series: [],
|
||||
timeRange: getDefaultTimeRange(),
|
||||
})
|
||||
);
|
||||
|
||||
let sentRequest: DataQueryRequest | undefined;
|
||||
|
||||
jest.mock('app/features/query/state/runRequest', () => ({
|
||||
runRequest: (ds: DataSourceApi, request: DataQueryRequest) => {
|
||||
sentRequest = request;
|
||||
return runRequest(ds, request);
|
||||
},
|
||||
}));
|
||||
|
||||
describe('SceneQueryRunner', () => {
|
||||
describe('when activated and got no data', () => {
|
||||
it('should run queries', async () => {
|
||||
const queryRunner = new SceneQueryRunner({
|
||||
queries: [{ refId: 'A' }],
|
||||
$timeRange: new SceneTimeRange(),
|
||||
});
|
||||
|
||||
expect(queryRunner.state.data).toBeUndefined();
|
||||
|
||||
queryRunner.activate();
|
||||
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
|
||||
expect(queryRunner.state.data?.state).toBe(LoadingState.Done);
|
||||
// Default max data points
|
||||
expect(sentRequest?.maxDataPoints).toBe(500);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when activated and maxDataPointsFromWidth set to true', () => {
|
||||
it('should run queries', async () => {
|
||||
const queryRunner = new SceneQueryRunner({
|
||||
queries: [{ refId: 'A' }],
|
||||
$timeRange: new SceneTimeRange(),
|
||||
maxDataPointsFromWidth: true,
|
||||
});
|
||||
|
||||
expect(queryRunner.state.data).toBeUndefined();
|
||||
|
||||
queryRunner.activate();
|
||||
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
|
||||
expect(queryRunner.state.data?.state).toBeUndefined();
|
||||
|
||||
queryRunner.setContainerWidth(1000);
|
||||
|
||||
expect(queryRunner.state.data?.state).toBeUndefined();
|
||||
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
|
||||
expect(queryRunner.state.data?.state).toBe(LoadingState.Done);
|
||||
});
|
||||
});
|
||||
});
|
@ -24,6 +24,11 @@ import { VariableDependencyConfig } from '../variables/VariableDependencyConfig'
|
||||
export interface QueryRunnerState extends SceneObjectStatePlain {
|
||||
data?: PanelData;
|
||||
queries: DataQueryExtended[];
|
||||
datasource?: DataSourceRef;
|
||||
minInterval?: string;
|
||||
maxDataPoints?: number;
|
||||
// Non persisted state
|
||||
maxDataPointsFromWidth?: boolean;
|
||||
}
|
||||
|
||||
export interface DataQueryExtended extends DataQuery {
|
||||
@ -31,7 +36,8 @@ export interface DataQueryExtended extends DataQuery {
|
||||
}
|
||||
|
||||
export class SceneQueryRunner extends SceneObjectBase<QueryRunnerState> {
|
||||
private querySub?: Unsubscribable;
|
||||
private _querySub?: Unsubscribable;
|
||||
private _containerWidth?: number;
|
||||
|
||||
protected _variableDependency = new VariableDependencyConfig(this, {
|
||||
statePaths: ['queries'],
|
||||
@ -51,17 +57,52 @@ export class SceneQueryRunner extends SceneObjectBase<QueryRunnerState> {
|
||||
})
|
||||
);
|
||||
|
||||
if (!this.state.data) {
|
||||
if (this.shouldRunQueriesOnActivate()) {
|
||||
this.runQueries();
|
||||
}
|
||||
}
|
||||
|
||||
private shouldRunQueriesOnActivate() {
|
||||
// If we already have data, no need
|
||||
// TODO validate that time range is similar and if not we should run queries again
|
||||
if (this.state.data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If no maxDataPoints specified we need might to wait for container width to be set from the outside
|
||||
if (!this.state.maxDataPoints && this.state.maxDataPointsFromWidth && !this._containerWidth) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public deactivate(): void {
|
||||
super.deactivate();
|
||||
|
||||
if (this.querySub) {
|
||||
this.querySub.unsubscribe();
|
||||
this.querySub = undefined;
|
||||
if (this._querySub) {
|
||||
this._querySub.unsubscribe();
|
||||
this._querySub = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public setContainerWidth(width: number) {
|
||||
// If we don't have a width we should run queries
|
||||
if (!this._containerWidth) {
|
||||
this._containerWidth = width;
|
||||
|
||||
// If we don't have maxDataPoints specifically set and maxDataPointsFromWidth is true
|
||||
if (this.state.maxDataPointsFromWidth && !this.state.maxDataPoints) {
|
||||
// As this is called from render path we need to wait for next tick before running queries
|
||||
setTimeout(() => {
|
||||
if (this.isActive && !this._querySub) {
|
||||
this.runQueries();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
} else {
|
||||
// let's just remember the width until next query issue
|
||||
this._containerWidth = width;
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,8 +111,12 @@ export class SceneQueryRunner extends SceneObjectBase<QueryRunnerState> {
|
||||
this.runWithTimeRange(timeRange.state);
|
||||
}
|
||||
|
||||
private getMaxDataPoints() {
|
||||
return this.state.maxDataPoints ?? this._containerWidth ?? 500;
|
||||
}
|
||||
|
||||
private async runWithTimeRange(timeRange: TimeRange) {
|
||||
const queries = cloneDeep(this.state.queries);
|
||||
const { datasource, minInterval, queries } = this.state;
|
||||
|
||||
const request: DataQueryRequest = {
|
||||
app: CoreApp.Dashboard,
|
||||
@ -82,14 +127,14 @@ export class SceneQueryRunner extends SceneObjectBase<QueryRunnerState> {
|
||||
range: timeRange,
|
||||
interval: '1s',
|
||||
intervalMs: 1000,
|
||||
targets: cloneDeep(this.state.queries),
|
||||
maxDataPoints: 500,
|
||||
targets: cloneDeep(queries),
|
||||
maxDataPoints: this.getMaxDataPoints(),
|
||||
scopedVars: {},
|
||||
startTime: Date.now(),
|
||||
};
|
||||
|
||||
try {
|
||||
const ds = await getDataSource(queries[0].datasource!, request.scopedVars);
|
||||
const ds = await getDataSource(datasource, request.scopedVars);
|
||||
|
||||
// Attach the data source name to each query
|
||||
request.targets = request.targets.map((query) => {
|
||||
@ -99,8 +144,9 @@ export class SceneQueryRunner extends SceneObjectBase<QueryRunnerState> {
|
||||
return query;
|
||||
});
|
||||
|
||||
const lowerIntervalLimit = ds.interval;
|
||||
const norm = rangeUtil.calculateInterval(timeRange, request.maxDataPoints ?? 1000, lowerIntervalLimit);
|
||||
// TODO interpolate minInterval
|
||||
const lowerIntervalLimit = minInterval ? minInterval : ds.interval;
|
||||
const norm = rangeUtil.calculateInterval(timeRange, request.maxDataPoints!, lowerIntervalLimit);
|
||||
|
||||
// make shallow copy of scoped vars,
|
||||
// and add built in variables interval and interval_ms
|
||||
@ -112,22 +158,20 @@ export class SceneQueryRunner extends SceneObjectBase<QueryRunnerState> {
|
||||
request.interval = norm.interval;
|
||||
request.intervalMs = norm.intervalMs;
|
||||
|
||||
this.querySub = runRequest(ds, request).subscribe({
|
||||
next: (data) => {
|
||||
console.log('set data', data, data.state);
|
||||
this.setState({ data });
|
||||
},
|
||||
this._querySub = runRequest(ds, request).subscribe({
|
||||
next: this.onDataReceived,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('PanelQueryRunner Error', err);
|
||||
}
|
||||
}
|
||||
|
||||
private onDataReceived = (data: PanelData) => {
|
||||
this.setState({ data });
|
||||
};
|
||||
}
|
||||
|
||||
async function getDataSource(
|
||||
datasource: DataSourceRef | string | DataSourceApi | null,
|
||||
scopedVars: ScopedVars
|
||||
): Promise<DataSourceApi> {
|
||||
async function getDataSource(datasource: DataSourceRef | undefined, scopedVars: ScopedVars): Promise<DataSourceApi> {
|
||||
if (datasource && (datasource as any).query) {
|
||||
return datasource as DataSourceApi;
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { getDefaultTimeRange } from '@grafana/data';
|
||||
|
||||
import { Scene } from '../components/Scene';
|
||||
import { SceneCanvasText } from '../components/SceneCanvasText';
|
||||
import { ScenePanelRepeater } from '../components/ScenePanelRepeater';
|
||||
import { SceneTimePicker } from '../components/SceneTimePicker';
|
||||
import { SceneToolbarInput } from '../components/SceneToolbarButton';
|
||||
import { VizPanel } from '../components/VizPanel';
|
||||
import { SceneFlexLayout } from '../components/layout/SceneFlexLayout';
|
||||
import {
|
||||
Scene,
|
||||
SceneCanvasText,
|
||||
ScenePanelRepeater,
|
||||
SceneTimePicker,
|
||||
SceneToolbarInput,
|
||||
SceneFlexLayout,
|
||||
VizPanel,
|
||||
} from '../components';
|
||||
import { SceneTimeRange } from '../core/SceneTimeRange';
|
||||
import { SceneEditManager } from '../editor/SceneEditManager';
|
||||
|
||||
@ -22,8 +22,8 @@ export function getFlexLayoutTest(): Scene {
|
||||
size: { minWidth: '70%' },
|
||||
pluginId: 'timeseries',
|
||||
title: 'Dynamic height and width',
|
||||
$data: getQueryRunnerWithRandomWalkQuery({}, { maxDataPointsFromWidth: true }),
|
||||
}),
|
||||
|
||||
new SceneFlexLayout({
|
||||
direction: 'column',
|
||||
children: [
|
||||
@ -51,7 +51,7 @@ export function getFlexLayoutTest(): Scene {
|
||||
],
|
||||
}),
|
||||
$editor: new SceneEditManager({}),
|
||||
$timeRange: new SceneTimeRange(getDefaultTimeRange()),
|
||||
$timeRange: new SceneTimeRange(),
|
||||
$data: getQueryRunnerWithRandomWalkQuery(),
|
||||
actions: [new SceneTimePicker({})],
|
||||
});
|
||||
@ -97,7 +97,7 @@ export function getScenePanelRepeaterTest(): Scene {
|
||||
}),
|
||||
}),
|
||||
$editor: new SceneEditManager({}),
|
||||
$timeRange: new SceneTimeRange(getDefaultTimeRange()),
|
||||
$timeRange: new SceneTimeRange(),
|
||||
$data: queryRunner,
|
||||
actions: [
|
||||
new SceneToolbarInput({
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { getDefaultTimeRange } from '@grafana/data';
|
||||
|
||||
import { VizPanel } from '../components';
|
||||
import { Scene } from '../components/Scene';
|
||||
import { SceneTimePicker } from '../components/SceneTimePicker';
|
||||
import { VizPanel } from '../components/VizPanel';
|
||||
import { SceneFlexLayout } from '../components/layout/SceneFlexLayout';
|
||||
import { SceneGridLayout } from '../components/layout/SceneGridLayout';
|
||||
import { SceneTimeRange } from '../core/SceneTimeRange';
|
||||
@ -57,7 +55,7 @@ export function getGridLayoutTest(): Scene {
|
||||
],
|
||||
}),
|
||||
$editor: new SceneEditManager({}),
|
||||
$timeRange: new SceneTimeRange(getDefaultTimeRange()),
|
||||
$timeRange: new SceneTimeRange(),
|
||||
$data: getQueryRunnerWithRandomWalkQuery(),
|
||||
actions: [new SceneTimePicker({})],
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { dateTime, getDefaultTimeRange } from '@grafana/data';
|
||||
import { dateTime } from '@grafana/data';
|
||||
|
||||
import { VizPanel } from '../components';
|
||||
import { Scene } from '../components/Scene';
|
||||
import { SceneTimePicker } from '../components/SceneTimePicker';
|
||||
import { VizPanel } from '../components/VizPanel';
|
||||
import { SceneGridLayout, SceneGridRow } from '../components/layout/SceneGridLayout';
|
||||
import { SceneTimeRange } from '../core/SceneTimeRange';
|
||||
import { SceneEditManager } from '../editor/SceneEditManager';
|
||||
@ -10,7 +10,7 @@ import { SceneEditManager } from '../editor/SceneEditManager';
|
||||
import { getQueryRunnerWithRandomWalkQuery } from './queries';
|
||||
|
||||
export function getGridWithMultipleTimeRanges(): Scene {
|
||||
const globalTimeRange = new SceneTimeRange(getDefaultTimeRange());
|
||||
const globalTimeRange = new SceneTimeRange();
|
||||
|
||||
const now = dateTime();
|
||||
const row1TimeRange = new SceneTimeRange({
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { getDefaultTimeRange } from '@grafana/data';
|
||||
|
||||
import { VizPanel } from '../components';
|
||||
import { Scene } from '../components/Scene';
|
||||
import { SceneTimePicker } from '../components/SceneTimePicker';
|
||||
import { VizPanel } from '../components/VizPanel';
|
||||
import { SceneFlexLayout } from '../components/layout/SceneFlexLayout';
|
||||
import { SceneGridLayout } from '../components/layout/SceneGridLayout';
|
||||
import { SceneTimeRange } from '../core/SceneTimeRange';
|
||||
@ -101,7 +99,7 @@ export function getMultipleGridLayoutTest(): Scene {
|
||||
}),
|
||||
|
||||
$editor: new SceneEditManager({}),
|
||||
$timeRange: new SceneTimeRange(getDefaultTimeRange()),
|
||||
$timeRange: new SceneTimeRange(),
|
||||
$data: getQueryRunnerWithRandomWalkQuery(),
|
||||
actions: [new SceneTimePicker({})],
|
||||
});
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { getDefaultTimeRange } from '@grafana/data';
|
||||
|
||||
import { VizPanel } from '../components';
|
||||
import { Scene } from '../components/Scene';
|
||||
import { SceneTimePicker } from '../components/SceneTimePicker';
|
||||
import { VizPanel } from '../components/VizPanel';
|
||||
import { SceneGridLayout, SceneGridRow } from '../components/layout/SceneGridLayout';
|
||||
import { SceneTimeRange } from '../core/SceneTimeRange';
|
||||
import { SceneEditManager } from '../editor/SceneEditManager';
|
||||
@ -15,7 +13,7 @@ export function getGridWithMultipleData(): Scene {
|
||||
layout: new SceneGridLayout({
|
||||
children: [
|
||||
new SceneGridRow({
|
||||
$timeRange: new SceneTimeRange(getDefaultTimeRange()),
|
||||
$timeRange: new SceneTimeRange(),
|
||||
$data: getQueryRunnerWithRandomWalkQuery({ scenarioId: 'random_walk_table' }),
|
||||
title: 'Row A - has its own query',
|
||||
key: 'Row A',
|
||||
@ -95,7 +93,7 @@ export function getGridWithMultipleData(): Scene {
|
||||
],
|
||||
}),
|
||||
$editor: new SceneEditManager({}),
|
||||
$timeRange: new SceneTimeRange(getDefaultTimeRange()),
|
||||
$timeRange: new SceneTimeRange(),
|
||||
$data: getQueryRunnerWithRandomWalkQuery(),
|
||||
actions: [new SceneTimePicker({})],
|
||||
});
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { getDefaultTimeRange } from '@grafana/data';
|
||||
|
||||
import { VizPanel } from '../components';
|
||||
import { Scene } from '../components/Scene';
|
||||
import { SceneTimePicker } from '../components/SceneTimePicker';
|
||||
import { VizPanel } from '../components/VizPanel';
|
||||
import { SceneGridLayout, SceneGridRow } from '../components/layout/SceneGridLayout';
|
||||
import { SceneTimeRange } from '../core/SceneTimeRange';
|
||||
import { SceneEditManager } from '../editor/SceneEditManager';
|
||||
@ -78,7 +76,7 @@ export function getGridWithRowLayoutTest(): Scene {
|
||||
],
|
||||
}),
|
||||
$editor: new SceneEditManager({}),
|
||||
$timeRange: new SceneTimeRange(getDefaultTimeRange()),
|
||||
$timeRange: new SceneTimeRange(),
|
||||
$data: getQueryRunnerWithRandomWalkQuery(),
|
||||
actions: [new SceneTimePicker({})],
|
||||
});
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { getDefaultTimeRange } from '@grafana/data';
|
||||
|
||||
import { VizPanel } from '../components';
|
||||
import { Scene } from '../components/Scene';
|
||||
import { SceneTimePicker } from '../components/SceneTimePicker';
|
||||
import { VizPanel } from '../components/VizPanel';
|
||||
import { SceneFlexLayout } from '../components/layout/SceneFlexLayout';
|
||||
import { SceneGridLayout, SceneGridRow } from '../components/layout/SceneGridLayout';
|
||||
import { SceneTimeRange } from '../core/SceneTimeRange';
|
||||
@ -83,7 +81,7 @@ export function getGridWithRowsTest(): Scene {
|
||||
children: [cell1, cell2, row1, row2],
|
||||
}),
|
||||
$editor: new SceneEditManager({}),
|
||||
$timeRange: new SceneTimeRange(getDefaultTimeRange()),
|
||||
$timeRange: new SceneTimeRange(),
|
||||
$data: getQueryRunnerWithRandomWalkQuery(),
|
||||
actions: [new SceneTimePicker({})],
|
||||
});
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { getDefaultTimeRange } from '@grafana/data';
|
||||
|
||||
import { VizPanel } from '../components';
|
||||
import { NestedScene } from '../components/NestedScene';
|
||||
import { Scene } from '../components/Scene';
|
||||
import { SceneTimePicker } from '../components/SceneTimePicker';
|
||||
import { VizPanel } from '../components/VizPanel';
|
||||
import { SceneFlexLayout } from '../components/layout/SceneFlexLayout';
|
||||
import { SceneTimeRange } from '../core/SceneTimeRange';
|
||||
|
||||
@ -23,7 +21,7 @@ export function getNestedScene(): Scene {
|
||||
}),
|
||||
],
|
||||
}),
|
||||
$timeRange: new SceneTimeRange(getDefaultTimeRange()),
|
||||
$timeRange: new SceneTimeRange(),
|
||||
$data: getQueryRunnerWithRandomWalkQuery(),
|
||||
actions: [new SceneTimePicker({})],
|
||||
});
|
||||
@ -46,7 +44,7 @@ export function getInnerScene(title: string) {
|
||||
}),
|
||||
],
|
||||
}),
|
||||
$timeRange: new SceneTimeRange(getDefaultTimeRange()),
|
||||
$timeRange: new SceneTimeRange(),
|
||||
$data: getQueryRunnerWithRandomWalkQuery(),
|
||||
actions: [new SceneTimePicker({})],
|
||||
});
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { TestDataQuery } from 'app/plugins/datasource/testdata/types';
|
||||
|
||||
import { SceneQueryRunner } from '../querying/SceneQueryRunner';
|
||||
import { QueryRunnerState, SceneQueryRunner } from '../querying/SceneQueryRunner';
|
||||
|
||||
export function getQueryRunnerWithRandomWalkQuery(overrides?: Partial<TestDataQuery>) {
|
||||
export function getQueryRunnerWithRandomWalkQuery(
|
||||
overrides?: Partial<TestDataQuery>,
|
||||
queryRunnerOverrides?: Partial<QueryRunnerState>
|
||||
) {
|
||||
return new SceneQueryRunner({
|
||||
queries: [
|
||||
{
|
||||
@ -15,5 +18,6 @@ export function getQueryRunnerWithRandomWalkQuery(overrides?: Partial<TestDataQu
|
||||
...overrides,
|
||||
},
|
||||
],
|
||||
...queryRunnerOverrides,
|
||||
});
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { getDefaultTimeRange } from '@grafana/data';
|
||||
|
||||
import { VizPanel } from '../components';
|
||||
import { NestedScene } from '../components/NestedScene';
|
||||
import { Scene } from '../components/Scene';
|
||||
import { SceneTimePicker } from '../components/SceneTimePicker';
|
||||
import { VizPanel } from '../components/VizPanel';
|
||||
import { SceneFlexLayout } from '../components/layout/SceneFlexLayout';
|
||||
import { SceneTimeRange } from '../core/SceneTimeRange';
|
||||
import { SceneEditManager } from '../editor/SceneEditManager';
|
||||
@ -55,7 +53,7 @@ export function getSceneWithRows(): Scene {
|
||||
],
|
||||
}),
|
||||
$editor: new SceneEditManager({}),
|
||||
$timeRange: new SceneTimeRange(getDefaultTimeRange()),
|
||||
$timeRange: new SceneTimeRange(),
|
||||
$data: getQueryRunnerWithRandomWalkQuery(),
|
||||
actions: [new SceneTimePicker({})],
|
||||
});
|
||||
|
@ -1,10 +1,8 @@
|
||||
import { getDefaultTimeRange } from '@grafana/data';
|
||||
|
||||
import { VizPanel } from '../components';
|
||||
import { Scene } from '../components/Scene';
|
||||
import { SceneCanvasText } from '../components/SceneCanvasText';
|
||||
import { SceneSubMenu } from '../components/SceneSubMenu';
|
||||
import { SceneTimePicker } from '../components/SceneTimePicker';
|
||||
import { VizPanel } from '../components/VizPanel';
|
||||
import { SceneFlexLayout } from '../components/layout/SceneFlexLayout';
|
||||
import { SceneTimeRange } from '../core/SceneTimeRange';
|
||||
import { VariableValueSelectors } from '../variables/components/VariableValueSelectors';
|
||||
@ -68,7 +66,7 @@ export function getVariablesDemo(): Scene {
|
||||
}),
|
||||
],
|
||||
}),
|
||||
$timeRange: new SceneTimeRange(getDefaultTimeRange()),
|
||||
$timeRange: new SceneTimeRange(),
|
||||
actions: [new SceneTimePicker({})],
|
||||
subMenu: new SceneSubMenu({
|
||||
children: [new VariableValueSelectors({})],
|
||||
|
Loading…
Reference in New Issue
Block a user