mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: visualizing data when creating/editing alerting rule. (#34191)
* VizWrapper to handle some state * alertingqueryrunner first edition * added so we always set name and uid when changing datasource. * wip. * wip * added support for canceling requests. * util for getting time ranges for expression queries * remove logs, store data in state * merge from run branch * incremental commit * viz working * added structure for marble testing. * paddings and move viz picker * less height for viz, less width on rows * change so the expression buttons doesnt submit form. * fixed run button. * replaced mocks with implementation that will set default query + expression. * merge with run queries * fixed so we set a datasource name for the default expression rule. * improving expression guard. * lots of styling fixes for viz * adding placeholder for relative time range. * fixed story. * added basic structure to handle open/close of time range picker. * removed section from TimeOptions since it isn't used any where. * adding mapper and tests * move relativetimepicker to its own dir * added some simple tests. * changed test. * use relativetimerangeinput * redo state management * refactored the tests. * replace timerange with relativetimerange * wip * wip * did some refactoring. * refactored time option formatting. * added proper formatting and display of time range. * add relative time description, slight refactor of height * fixed incorrect import. * added validator and changed formatting. * removed unused dep. * reverted back to internal function. * fixed display of relative time range picker. * fixed failing tests. * fixed parsing issue. * fixed position of time range picker. * some more refactorings. * fixed validation of really big values. * added another test. * restored the step2 check. * fixed merge issue. * Update packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com> * reverted change. * fixed merge conflict. * fixed todo. * sort some paddings * replace theme with theme2 Co-authored-by: Peter Holmberg <peter.hlmbrg@gmail.com> Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com>
This commit is contained in:
@@ -59,6 +59,7 @@ export function RadioButtonGroup<T>({
|
|||||||
fullWidth={fullWidth}
|
fullWidth={fullWidth}
|
||||||
>
|
>
|
||||||
{o.icon && <Icon name={o.icon as IconName} className={styles.icon} />}
|
{o.icon && <Icon name={o.icon as IconName} className={styles.icon} />}
|
||||||
|
{o.imgUrl && <img src={o.imgUrl} alt={o.label} className={styles.img} />}
|
||||||
{o.label}
|
{o.label}
|
||||||
</RadioButton>
|
</RadioButton>
|
||||||
);
|
);
|
||||||
@@ -85,5 +86,10 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
icon: css`
|
icon: css`
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
`,
|
`,
|
||||||
|
img: css`
|
||||||
|
width: ${theme.spacing(2)};
|
||||||
|
height: ${theme.spacing(2)};
|
||||||
|
margin-right: ${theme.spacing(1)};
|
||||||
|
`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -155,11 +155,13 @@ export class AlertingQueryEditor extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { value = [] } = this.props;
|
const { value = [] } = this.props;
|
||||||
|
const { panelDataByRefId } = this.state;
|
||||||
const styles = getStyles(config.theme2);
|
const styles = getStyles(config.theme2);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<AlertingQueryRows
|
<AlertingQueryRows
|
||||||
|
data={panelDataByRefId}
|
||||||
queries={value}
|
queries={value}
|
||||||
onQueriesChange={this.props.onChange}
|
onQueriesChange={this.props.onChange}
|
||||||
onDuplicateQuery={this.onDuplicateQuery}
|
onDuplicateQuery={this.onDuplicateQuery}
|
||||||
@@ -197,6 +199,7 @@ const defaultTimeRange = (model: DataQuery): RelativeTimeRange | undefined => {
|
|||||||
if (isExpressionQuery(model)) {
|
if (isExpressionQuery(model)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getDefaultRelativeTimeRange();
|
return getDefaultRelativeTimeRange();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,14 @@
|
|||||||
import React, { PureComponent, ReactNode } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd';
|
import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd';
|
||||||
import {
|
import { DataQuery, DataSourceInstanceSettings, PanelData, RelativeTimeRange } from '@grafana/data';
|
||||||
DataQuery,
|
|
||||||
DataSourceInstanceSettings,
|
|
||||||
getDefaultRelativeTimeRange,
|
|
||||||
PanelData,
|
|
||||||
RelativeTimeRange,
|
|
||||||
} from '@grafana/data';
|
|
||||||
import { getDataSourceSrv } from '@grafana/runtime';
|
import { getDataSourceSrv } from '@grafana/runtime';
|
||||||
import { QueryEditorRow } from 'app/features/query/components/QueryEditorRow';
|
import { AlertingQueryWrapper } from './AlertingQueryWrapper';
|
||||||
import { isExpressionQuery } from 'app/features/expressions/guards';
|
|
||||||
import { GrafanaQuery } from 'app/types/unified-alerting-dto';
|
import { GrafanaQuery } from 'app/types/unified-alerting-dto';
|
||||||
import { RelativeTimeRangePicker } from '@grafana/ui';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
// The query configuration
|
// The query configuration
|
||||||
queries: GrafanaQuery[];
|
queries: GrafanaQuery[];
|
||||||
|
data: Record<string, PanelData>;
|
||||||
|
|
||||||
// Query editing
|
// Query editing
|
||||||
onQueriesChange: (queries: GrafanaQuery[]) => void;
|
onQueriesChange: (queries: GrafanaQuery[]) => void;
|
||||||
@@ -30,6 +23,7 @@ interface State {
|
|||||||
export class AlertingQueryRows extends PureComponent<Props, State> {
|
export class AlertingQueryRows extends PureComponent<Props, State> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = { dataPerQuery: {} };
|
this.state = { dataPerQuery: {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +31,7 @@ export class AlertingQueryRows extends PureComponent<Props, State> {
|
|||||||
this.props.onQueriesChange(this.props.queries.filter((item) => item.model !== query));
|
this.props.onQueriesChange(this.props.queries.filter((item) => item.model !== query));
|
||||||
};
|
};
|
||||||
|
|
||||||
onChangeTimeRange(timeRange: RelativeTimeRange, index: number) {
|
onChangeTimeRange = (timeRange: RelativeTimeRange, index: number) => {
|
||||||
const { queries, onQueriesChange } = this.props;
|
const { queries, onQueriesChange } = this.props;
|
||||||
onQueriesChange(
|
onQueriesChange(
|
||||||
queries.map((item, itemIndex) => {
|
queries.map((item, itemIndex) => {
|
||||||
@@ -50,9 +44,9 @@ export class AlertingQueryRows extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
onChangeDataSource(settings: DataSourceInstanceSettings, index: number) {
|
onChangeDataSource = (settings: DataSourceInstanceSettings, index: number) => {
|
||||||
const { queries, onQueriesChange } = this.props;
|
const { queries, onQueriesChange } = this.props;
|
||||||
|
|
||||||
onQueriesChange(
|
onQueriesChange(
|
||||||
@@ -79,9 +73,9 @@ export class AlertingQueryRows extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
onChangeQuery(query: DataQuery, index: number) {
|
onChangeQuery = (query: DataQuery, index: number) => {
|
||||||
const { queries, onQueriesChange } = this.props;
|
const { queries, onQueriesChange } = this.props;
|
||||||
|
|
||||||
onQueriesChange(
|
onQueriesChange(
|
||||||
@@ -100,7 +94,7 @@ export class AlertingQueryRows extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
onDragEnd = (result: DropResult) => {
|
onDragEnd = (result: DropResult) => {
|
||||||
const { queries, onQueriesChange } = this.props;
|
const { queries, onQueriesChange } = this.props;
|
||||||
@@ -133,7 +127,7 @@ export class AlertingQueryRows extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { queries } = this.props;
|
const { onDuplicateQuery, onRunQueries, queries } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragDropContext onDragEnd={this.onDragEnd}>
|
<DragDropContext onDragEnd={this.onDragEnd}>
|
||||||
@@ -141,8 +135,8 @@ export class AlertingQueryRows extends PureComponent<Props, State> {
|
|||||||
{(provided) => {
|
{(provided) => {
|
||||||
return (
|
return (
|
||||||
<div ref={provided.innerRef} {...provided.droppableProps}>
|
<div ref={provided.innerRef} {...provided.droppableProps}>
|
||||||
{queries.map((query: GrafanaQuery, index) => {
|
{queries.map((query, index) => {
|
||||||
const data = this.state.dataPerQuery[query.refId];
|
const data = this.props.data ? this.props.data[query.refId] : ({} as PanelData);
|
||||||
const dsSettings = this.getDataSourceSettings(query);
|
const dsSettings = this.getDataSourceSettings(query);
|
||||||
|
|
||||||
if (!dsSettings) {
|
if (!dsSettings) {
|
||||||
@@ -150,24 +144,19 @@ export class AlertingQueryRows extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryEditorRow
|
<AlertingQueryWrapper
|
||||||
dataSource={dsSettings}
|
|
||||||
onChangeDataSource={
|
|
||||||
!isExpressionQuery(query.model)
|
|
||||||
? (settings) => this.onChangeDataSource(settings, index)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
id={query.refId}
|
|
||||||
index={index}
|
index={index}
|
||||||
key={query.refId}
|
key={`${query.refId}-${index}`}
|
||||||
|
dsSettings={dsSettings}
|
||||||
data={data}
|
data={data}
|
||||||
query={query.model}
|
query={query}
|
||||||
onChange={(query) => this.onChangeQuery(query, index)}
|
onChangeQuery={this.onChangeQuery}
|
||||||
renderHeaderExtras={() => this.renderTimePicker(query, index)}
|
|
||||||
onRemoveQuery={this.onRemoveQuery}
|
onRemoveQuery={this.onRemoveQuery}
|
||||||
onAddQuery={(duplicate) => this.onDuplicateQuery(duplicate, query)}
|
|
||||||
onRunQuery={this.props.onRunQueries}
|
|
||||||
queries={queries}
|
queries={queries}
|
||||||
|
onChangeDataSource={this.onChangeDataSource}
|
||||||
|
onDuplicateQuery={onDuplicateQuery}
|
||||||
|
onRunQueries={onRunQueries}
|
||||||
|
onChangeTimeRange={this.onChangeTimeRange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -179,17 +168,4 @@ export class AlertingQueryRows extends PureComponent<Props, State> {
|
|||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTimePicker(query: GrafanaQuery, index: number): ReactNode {
|
|
||||||
if (isExpressionQuery(query.model)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RelativeTimeRangePicker
|
|
||||||
timeRange={query.relativeTimeRange ?? getDefaultRelativeTimeRange()}
|
|
||||||
onChange={(range) => this.onChangeTimeRange(range, index)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import React, { FC, ReactNode } from 'react';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import {
|
||||||
|
DataQuery,
|
||||||
|
DataSourceInstanceSettings,
|
||||||
|
GrafanaTheme2,
|
||||||
|
PanelData,
|
||||||
|
RelativeTimeRange,
|
||||||
|
getDefaultRelativeTimeRange,
|
||||||
|
} from '@grafana/data';
|
||||||
|
import { useStyles2, RelativeTimeRangePicker } from '@grafana/ui';
|
||||||
|
import { QueryEditorRow } from '../../query/components/QueryEditorRow';
|
||||||
|
import { VizWrapper } from '../unified/components/rule-editor/VizWrapper';
|
||||||
|
import { isExpressionQuery } from '../../expressions/guards';
|
||||||
|
import { GrafanaQuery } from 'app/types/unified-alerting-dto';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: PanelData;
|
||||||
|
query: GrafanaQuery;
|
||||||
|
queries: GrafanaQuery[];
|
||||||
|
dsSettings: DataSourceInstanceSettings;
|
||||||
|
onChangeDataSource: (settings: DataSourceInstanceSettings, index: number) => void;
|
||||||
|
onChangeQuery: (query: DataQuery, index: number) => void;
|
||||||
|
onChangeTimeRange?: (timeRange: RelativeTimeRange, index: number) => void;
|
||||||
|
onRemoveQuery: (query: DataQuery) => void;
|
||||||
|
onDuplicateQuery: (query: GrafanaQuery) => void;
|
||||||
|
onRunQueries: () => void;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlertingQueryWrapper: FC<Props> = ({
|
||||||
|
data,
|
||||||
|
dsSettings,
|
||||||
|
index,
|
||||||
|
onChangeDataSource,
|
||||||
|
onChangeQuery,
|
||||||
|
onChangeTimeRange,
|
||||||
|
onRunQueries,
|
||||||
|
onRemoveQuery,
|
||||||
|
onDuplicateQuery,
|
||||||
|
query,
|
||||||
|
queries,
|
||||||
|
}) => {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
const isExpression = isExpressionQuery(query.model);
|
||||||
|
|
||||||
|
const renderTimePicker = (query: GrafanaQuery, index: number): ReactNode => {
|
||||||
|
if (isExpressionQuery(query.model) || !onChangeTimeRange) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RelativeTimeRangePicker
|
||||||
|
timeRange={query.relativeTimeRange ?? getDefaultRelativeTimeRange()}
|
||||||
|
onChange={(range) => onChangeTimeRange(range, index)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<QueryEditorRow
|
||||||
|
dataSource={dsSettings}
|
||||||
|
onChangeDataSource={!isExpression ? (settings) => onChangeDataSource(settings, index) : undefined}
|
||||||
|
id={query.refId}
|
||||||
|
index={index}
|
||||||
|
key={query.refId}
|
||||||
|
data={data}
|
||||||
|
query={query.model}
|
||||||
|
onChange={(query) => onChangeQuery(query, index)}
|
||||||
|
onRemoveQuery={onRemoveQuery}
|
||||||
|
onAddQuery={onDuplicateQuery}
|
||||||
|
onRunQuery={onRunQueries}
|
||||||
|
queries={queries}
|
||||||
|
renderHeaderExtras={() => renderTimePicker(query, index)}
|
||||||
|
/>
|
||||||
|
{data && <VizWrapper data={data} defaultPanel={isExpression ? 'table' : 'timeseries'} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
|
wrapper: css`
|
||||||
|
margin-bottom: ${theme.spacing(1)};
|
||||||
|
border: 1px solid ${theme.colors.border.medium};
|
||||||
|
border-radius: ${theme.shape.borderRadius(1)};
|
||||||
|
padding-bottom: ${theme.spacing(1)};
|
||||||
|
`,
|
||||||
|
});
|
||||||
@@ -57,6 +57,7 @@ export class AlertingQueryRunner {
|
|||||||
this.lastResult = nextResult;
|
this.lastResult = nextResult;
|
||||||
this.subject.next(this.lastResult);
|
this.subject.next(this.lastResult);
|
||||||
},
|
},
|
||||||
|
|
||||||
error: (error: Error) => {
|
error: (error: Error) => {
|
||||||
this.lastResult = mapErrorToPanelData(this.lastResult, error);
|
this.lastResult = mapErrorToPanelData(this.lastResult, error);
|
||||||
this.subject.next(this.lastResult);
|
this.subject.next(this.lastResult);
|
||||||
@@ -92,6 +93,7 @@ export class AlertingQueryRunner {
|
|||||||
if (this.subject) {
|
if (this.subject) {
|
||||||
this.subject.complete();
|
this.subject.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cancel();
|
this.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import React, { FC, useState } from 'react';
|
||||||
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { GrafanaTheme2, PanelData, VizOrientation } from '@grafana/data';
|
||||||
|
import { config, PanelRenderer } from '@grafana/runtime';
|
||||||
|
import {
|
||||||
|
LegendDisplayMode,
|
||||||
|
SingleStatBaseOptions,
|
||||||
|
TooltipDisplayMode,
|
||||||
|
RadioButtonGroup,
|
||||||
|
useStyles2,
|
||||||
|
} from '@grafana/ui';
|
||||||
|
|
||||||
|
const TIMESERIES = 'timeseries';
|
||||||
|
const TABLE = 'table';
|
||||||
|
const STAT = 'stat';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: PanelData;
|
||||||
|
defaultPanel?: 'timeseries' | 'table' | 'stat';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VizWrapper: FC<Props> = ({ data, defaultPanel }) => {
|
||||||
|
const [pluginId, changePluginId] = useState<string>(defaultPanel ?? TIMESERIES);
|
||||||
|
const options = { ...getOptionsForPanelPlugin(pluginId) };
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
const panels = getSupportedPanels();
|
||||||
|
|
||||||
|
if (!options || !data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<div className={styles.buttonGroup}>
|
||||||
|
<RadioButtonGroup options={panels} value={pluginId} onChange={changePluginId} />
|
||||||
|
</div>
|
||||||
|
<div style={{ height: '200px', width: '100%' }}>
|
||||||
|
<AutoSizer style={{ width: '100%', height: '100%' }}>
|
||||||
|
{({ width, height }) => {
|
||||||
|
if (width === 0 || height === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<PanelRenderer
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
data={data}
|
||||||
|
pluginId={pluginId}
|
||||||
|
title="title"
|
||||||
|
onOptionsChange={() => {}}
|
||||||
|
options={options}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</AutoSizer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSupportedPanels = () => {
|
||||||
|
return Object.values(config.panels)
|
||||||
|
.filter((p) => p.id === TIMESERIES || p.id === TABLE || p.id === STAT)
|
||||||
|
.map((panel) => ({ value: panel.id, label: panel.name, imgUrl: panel.info.logos.small }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOptionsForPanelPlugin = (panelPlugin: string) => {
|
||||||
|
switch (panelPlugin) {
|
||||||
|
case STAT:
|
||||||
|
return singleStatOptions;
|
||||||
|
case TABLE:
|
||||||
|
return tableOptions;
|
||||||
|
case TIMESERIES:
|
||||||
|
return timeSeriesOptions;
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeSeriesOptions = {
|
||||||
|
legend: {
|
||||||
|
displayMode: LegendDisplayMode.List,
|
||||||
|
placement: 'bottom',
|
||||||
|
calcs: [],
|
||||||
|
},
|
||||||
|
tooltipOptions: {
|
||||||
|
mode: TooltipDisplayMode.Single,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableOptions = {
|
||||||
|
frameIndex: 0,
|
||||||
|
showHeader: true,
|
||||||
|
};
|
||||||
|
const singleStatOptions: SingleStatBaseOptions = {
|
||||||
|
reduceOptions: {
|
||||||
|
calcs: [],
|
||||||
|
},
|
||||||
|
orientation: VizOrientation.Auto,
|
||||||
|
text: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
|
wrapper: css`
|
||||||
|
padding: 0 ${theme.spacing(2)};
|
||||||
|
`,
|
||||||
|
buttonGroup: css`
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
`,
|
||||||
|
});
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
export const SAMPLE_QUERIES = [
|
||||||
|
{
|
||||||
|
refId: 'A',
|
||||||
|
queryType: '',
|
||||||
|
relativeTimeRange: {
|
||||||
|
from: 21600,
|
||||||
|
to: 0,
|
||||||
|
},
|
||||||
|
datasourceUid: '6_hUDNQGz',
|
||||||
|
model: {
|
||||||
|
intervalMs: 1000,
|
||||||
|
maxDataPoints: 100,
|
||||||
|
pulseWave: {
|
||||||
|
offCount: 6,
|
||||||
|
offValue: 1,
|
||||||
|
onCount: 6,
|
||||||
|
onValue: 10,
|
||||||
|
timeStep: 5,
|
||||||
|
},
|
||||||
|
refId: 'A',
|
||||||
|
scenarioId: 'predictable_pulse',
|
||||||
|
stringInput: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
refId: 'B',
|
||||||
|
queryType: '',
|
||||||
|
relativeTimeRange: {
|
||||||
|
from: 0,
|
||||||
|
to: 0,
|
||||||
|
},
|
||||||
|
datasourceUid: '-100',
|
||||||
|
model: {
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
evaluator: {
|
||||||
|
params: [3],
|
||||||
|
type: 'gt',
|
||||||
|
},
|
||||||
|
operator: {
|
||||||
|
type: 'and',
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
params: ['A'],
|
||||||
|
},
|
||||||
|
reducer: {
|
||||||
|
type: 'last',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
intervalMs: 1000,
|
||||||
|
maxDataPoints: 100,
|
||||||
|
refId: 'B',
|
||||||
|
type: 'classic_conditions',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -99,6 +99,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
`,
|
`,
|
||||||
current: css`
|
current: css`
|
||||||
label: currentVisualizationItem;
|
label: currentVisualizationItem;
|
||||||
|
border: 1px solid ${theme.colors.primary.border};
|
||||||
background: ${theme.colors.action.selected};
|
background: ${theme.colors.action.selected};
|
||||||
`,
|
`,
|
||||||
disabled: css`
|
disabled: css`
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function PanelRenderer<P extends object = any, F extends object = any>(pr
|
|||||||
return <div>Failed to load plugin: {error.message}</div>;
|
return <div>Failed to load plugin: {error.message}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (pluginIsLoading(loading, plugin, pluginId)) {
|
||||||
return <div>Loading plugin panel...</div>;
|
return <div>Loading plugin panel...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,11 +66,11 @@ export function PanelRenderer<P extends object = any, F extends object = any>(pr
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const useOptionDefaults = <P extends object = any, F extends object = any>(
|
function useOptionDefaults<P extends object = any, F extends object = any>(
|
||||||
plugin: PanelPlugin | undefined,
|
plugin: PanelPlugin | undefined,
|
||||||
options: P,
|
options: P,
|
||||||
fieldConfig: FieldConfigSource<F>
|
fieldConfig: FieldConfigSource<F>
|
||||||
): OptionDefaults | undefined => {
|
): OptionDefaults | undefined {
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (!plugin) {
|
if (!plugin) {
|
||||||
return;
|
return;
|
||||||
@@ -83,14 +83,14 @@ const useOptionDefaults = <P extends object = any, F extends object = any>(
|
|||||||
isAfterPluginChange: false,
|
isAfterPluginChange: false,
|
||||||
});
|
});
|
||||||
}, [plugin, fieldConfig, options]);
|
}, [plugin, fieldConfig, options]);
|
||||||
};
|
}
|
||||||
|
|
||||||
const useFieldOverrides = (
|
function useFieldOverrides(
|
||||||
plugin: PanelPlugin | undefined,
|
plugin: PanelPlugin | undefined,
|
||||||
defaultOptions: OptionDefaults | undefined,
|
defaultOptions: OptionDefaults | undefined,
|
||||||
data: PanelData | undefined,
|
data: PanelData | undefined,
|
||||||
timeZone: string
|
timeZone: string
|
||||||
): PanelData | undefined => {
|
): PanelData | undefined {
|
||||||
const fieldConfig = defaultOptions?.fieldConfig;
|
const fieldConfig = defaultOptions?.fieldConfig;
|
||||||
const series = data?.series;
|
const series = data?.series;
|
||||||
const fieldConfigRegistry = plugin?.fieldConfigRegistry;
|
const fieldConfigRegistry = plugin?.fieldConfigRegistry;
|
||||||
@@ -113,4 +113,8 @@ const useFieldOverrides = (
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}, [fieldConfigRegistry, fieldConfig, data, series, timeZone, theme]);
|
}, [fieldConfigRegistry, fieldConfig, data, series, timeZone, theme]);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
function pluginIsLoading(loading: boolean, plugin: PanelPlugin<any, any> | undefined, pluginId: string) {
|
||||||
|
return loading || plugin?.meta.id !== pluginId;
|
||||||
|
}
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export enum GrafanaAlertStateDecision {
|
|||||||
KeepLastState = 'KeepLastState',
|
KeepLastState = 'KeepLastState',
|
||||||
OK = 'OK',
|
OK = 'OK',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GrafanaQuery {
|
export interface GrafanaQuery {
|
||||||
refId: string;
|
refId: string;
|
||||||
queryType: string;
|
queryType: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user