mirror of
https://github.com/grafana/grafana.git
synced 2024-12-22 15:13:38 -06:00
Alerting: Improve performance ash page (#97619)
* first commit adding usememo and refactoring scenes objects to keep an state for variables values * Fix scenes with the panel reacting to variables changes * move body to the model * address some pr feedback * Refactoring central alert history scene (#97658) Refactoring * fix test and some wrong imports * update comments * add eslint-disable-next-line * remove unnecessary SceneFlexLayout and SceneFlexItem wrapper * address pr feedback * update tests for labels filtering --------- Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
parent
448d87157c
commit
dd9638cade
@ -1,14 +1,16 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
import { GrafanaTheme2, VariableHide } from '@grafana/data';
|
||||
import {
|
||||
CustomVariable,
|
||||
EmbeddedScene,
|
||||
PanelBuilders,
|
||||
SceneComponentProps,
|
||||
SceneControlsSpacer,
|
||||
SceneFlexItem,
|
||||
SceneFlexLayout,
|
||||
SceneObjectBase,
|
||||
SceneQueryRunner,
|
||||
SceneReactObject,
|
||||
SceneRefreshPicker,
|
||||
@ -16,7 +18,9 @@ import {
|
||||
SceneTimeRange,
|
||||
SceneVariableSet,
|
||||
TextBoxVariable,
|
||||
VariableDependencyConfig,
|
||||
VariableValueSelectors,
|
||||
sceneGraph,
|
||||
useUrlSync,
|
||||
} from '@grafana/scenes';
|
||||
import { GraphDrawStyle, VisibilityMode } from '@grafana/schema/dist/esm/index';
|
||||
@ -36,14 +40,13 @@ import {
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
|
||||
import { LogMessages, logInfo } from '../../../Analytics';
|
||||
import { DataSourceInformation } from '../../../home/Insights';
|
||||
|
||||
import { alertStateHistoryDatasource, useRegisterHistoryRuntimeDataSource } from './CentralHistoryRuntimeDataSource';
|
||||
import { HistoryEventsListObject } from './EventListSceneObject';
|
||||
|
||||
export const LABELS_FILTER = 'labelsFilter';
|
||||
export const STATE_FILTER_TO = 'stateFilterTo';
|
||||
export const STATE_FILTER_FROM = 'stateFilterFrom';
|
||||
export const LABELS_FILTER = 'LABELS_FILTER';
|
||||
export const STATE_FILTER_TO = 'STATE_FILTER_TO';
|
||||
export const STATE_FILTER_FROM = 'STATE_FILTER_FROM';
|
||||
/**
|
||||
*
|
||||
* This scene shows the history of the alert state changes.
|
||||
@ -67,74 +70,72 @@ export const CentralAlertHistoryScene = () => {
|
||||
logInfo(LogMessages.loadedCentralAlertStateHistory);
|
||||
}, []);
|
||||
|
||||
// create the variables for the filters
|
||||
// textbox variable for filtering by labels
|
||||
const labelsFilterVariable = new TextBoxVariable({
|
||||
name: LABELS_FILTER,
|
||||
label: 'Labels: ',
|
||||
});
|
||||
//custom variable for filtering by the current state
|
||||
const transitionsToFilterVariable = new CustomVariable({
|
||||
name: STATE_FILTER_TO,
|
||||
value: StateFilterValues.all,
|
||||
label: 'End state:',
|
||||
hide: VariableHide.dontHide,
|
||||
query: `All : ${StateFilterValues.all}, To Firing : ${StateFilterValues.firing},To Normal : ${StateFilterValues.normal},To Pending : ${StateFilterValues.pending}`,
|
||||
});
|
||||
//custom variable for filtering by the previous state
|
||||
const transitionsFromFilterVariable = new CustomVariable({
|
||||
name: STATE_FILTER_FROM,
|
||||
value: StateFilterValues.all,
|
||||
label: 'Start state:',
|
||||
hide: VariableHide.dontHide,
|
||||
query: `All : ${StateFilterValues.all}, From Firing : ${StateFilterValues.firing},From Normal : ${StateFilterValues.normal},From Pending : ${StateFilterValues.pending}`,
|
||||
});
|
||||
|
||||
useRegisterHistoryRuntimeDataSource(); // register the runtime datasource for the history api.
|
||||
|
||||
const scene = new EmbeddedScene({
|
||||
controls: [
|
||||
new SceneReactObject({
|
||||
component: LabelFilter,
|
||||
}),
|
||||
new SceneReactObject({
|
||||
component: FilterInfo,
|
||||
}),
|
||||
new VariableValueSelectors({}),
|
||||
new SceneReactObject({
|
||||
component: ClearFilterButton,
|
||||
props: {
|
||||
labelsFilterVariable,
|
||||
transitionsToFilterVariable,
|
||||
transitionsFromFilterVariable,
|
||||
},
|
||||
}),
|
||||
new SceneControlsSpacer(),
|
||||
new SceneTimePicker({}),
|
||||
new SceneRefreshPicker({}),
|
||||
],
|
||||
// use default time range as from 1 hour ago to now, as the limit of the history api is 5000 events,
|
||||
// and using a wider time range might lead to showing gaps in the events list and the chart.
|
||||
$timeRange: new SceneTimeRange({
|
||||
from: 'now-1h',
|
||||
to: 'now',
|
||||
}),
|
||||
$variables: new SceneVariableSet({
|
||||
variables: [labelsFilterVariable, transitionsFromFilterVariable, transitionsToFilterVariable],
|
||||
}),
|
||||
body: new SceneFlexLayout({
|
||||
direction: 'column',
|
||||
children: [
|
||||
new SceneFlexItem({
|
||||
ySizing: 'content',
|
||||
body: getEventsSceneObject(alertStateHistoryDatasource),
|
||||
const scene = useMemo(() => {
|
||||
// create the variables for the filters
|
||||
// textbox variable for filtering by labels
|
||||
const labelsFilterVariable = new TextBoxVariable({
|
||||
name: LABELS_FILTER,
|
||||
label: 'Labels: ',
|
||||
});
|
||||
|
||||
//custom variable for filtering by the current state
|
||||
const transitionsToFilterVariable = new CustomVariable({
|
||||
name: STATE_FILTER_TO,
|
||||
value: StateFilterValues.all,
|
||||
label: 'End state:',
|
||||
hide: VariableHide.dontHide,
|
||||
query: `All : ${StateFilterValues.all}, To Firing : ${StateFilterValues.firing},To Normal : ${StateFilterValues.normal},To Pending : ${StateFilterValues.pending}`,
|
||||
});
|
||||
|
||||
//custom variable for filtering by the previous state
|
||||
const transitionsFromFilterVariable = new CustomVariable({
|
||||
name: STATE_FILTER_FROM,
|
||||
value: StateFilterValues.all,
|
||||
label: 'Start state:',
|
||||
hide: VariableHide.dontHide,
|
||||
query: `All : ${StateFilterValues.all}, From Firing : ${StateFilterValues.firing},From Normal : ${StateFilterValues.normal},From Pending : ${StateFilterValues.pending}`,
|
||||
});
|
||||
|
||||
return new EmbeddedScene({
|
||||
controls: [
|
||||
new SceneReactObject({
|
||||
component: LabelFilter,
|
||||
}),
|
||||
new SceneFlexItem({
|
||||
body: new HistoryEventsListObject(),
|
||||
new SceneReactObject({
|
||||
component: FilterInfo,
|
||||
}),
|
||||
new VariableValueSelectors({}),
|
||||
new ClearFilterButtonScenesObject({}),
|
||||
new SceneControlsSpacer(),
|
||||
new SceneTimePicker({}),
|
||||
new SceneRefreshPicker({}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
// use default time range as from 1 hour ago to now, as the limit of the history api is 5000 events,
|
||||
// and using a wider time range might lead to showing gaps in the events list and the chart.
|
||||
$timeRange: new SceneTimeRange({
|
||||
from: 'now-1h',
|
||||
to: 'now',
|
||||
}),
|
||||
$variables: new SceneVariableSet({
|
||||
variables: [labelsFilterVariable, transitionsFromFilterVariable, transitionsToFilterVariable],
|
||||
}),
|
||||
body: new SceneFlexLayout({
|
||||
direction: 'column',
|
||||
children: [
|
||||
new SceneFlexItem({
|
||||
ySizing: 'content',
|
||||
body: getEventsSceneObject(),
|
||||
}),
|
||||
new SceneFlexItem({
|
||||
body: new HistoryEventsListObject({}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
}, []);
|
||||
|
||||
// we need to call this to sync the url with the scene state
|
||||
const isUrlSyncInitialized = useUrlSync(scene);
|
||||
|
||||
@ -147,22 +148,11 @@ export const CentralAlertHistoryScene = () => {
|
||||
/**
|
||||
* Creates a SceneFlexItem with a timeseries panel that shows the events.
|
||||
* The query uses a runtime datasource that fetches the events from the history api.
|
||||
* @param alertStateHistoryDataSource the datasource information for the runtime datasource
|
||||
*/
|
||||
function getEventsSceneObject(alertStateHistoryDataSource: DataSourceInformation) {
|
||||
return new EmbeddedScene({
|
||||
controls: [],
|
||||
body: new SceneFlexLayout({
|
||||
direction: 'column',
|
||||
children: [
|
||||
new SceneFlexItem({
|
||||
ySizing: 'content',
|
||||
body: new SceneFlexLayout({
|
||||
children: [getEventsScenesFlexItem(alertStateHistoryDataSource)],
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
function getEventsSceneObject() {
|
||||
return new SceneFlexLayout({
|
||||
direction: 'column',
|
||||
children: [getEventsScenesFlexItem()],
|
||||
});
|
||||
}
|
||||
|
||||
@ -171,15 +161,15 @@ function getEventsSceneObject(alertStateHistoryDataSource: DataSourceInformation
|
||||
* @param datasource the datasource information for the runtime datasource
|
||||
* @returns the SceneQueryRunner
|
||||
*/
|
||||
function getSceneQuery(datasource: DataSourceInformation) {
|
||||
function getQueryRunnerForAlertHistoryDataSource() {
|
||||
const query = new SceneQueryRunner({
|
||||
datasource: datasource,
|
||||
datasource: alertStateHistoryDatasource,
|
||||
queries: [
|
||||
{
|
||||
refId: 'A',
|
||||
expr: '',
|
||||
queryType: 'range',
|
||||
step: '10s',
|
||||
labels: '${LABELS_FILTER}',
|
||||
stateFrom: '${STATE_FILTER_FROM}',
|
||||
stateTo: '${STATE_FILTER_TO}',
|
||||
},
|
||||
],
|
||||
});
|
||||
@ -189,7 +179,7 @@ function getSceneQuery(datasource: DataSourceInformation) {
|
||||
* This function creates a SceneFlexItem with a timeseries panel that shows the events.
|
||||
* The query uses a runtime datasource that fetches the events from the history api.
|
||||
*/
|
||||
export function getEventsScenesFlexItem(datasource: DataSourceInformation) {
|
||||
export function getEventsScenesFlexItem() {
|
||||
return new SceneFlexItem({
|
||||
minHeight: 300,
|
||||
body: PanelBuilders.timeseries()
|
||||
@ -197,7 +187,7 @@ export function getEventsScenesFlexItem(datasource: DataSourceInformation) {
|
||||
.setDescription(
|
||||
'Each alert event represents an alert instance that changed its state at a particular point in time. The history of the data is displayed over a period of time.'
|
||||
)
|
||||
.setData(getSceneQuery(datasource))
|
||||
.setData(getQueryRunnerForAlertHistoryDataSource())
|
||||
.setColor({ mode: 'continuous-BlPu' })
|
||||
.setCustomFieldConfig('fillOpacity', 100)
|
||||
.setCustomFieldConfig('drawStyle', GraphDrawStyle.Bars)
|
||||
@ -213,47 +203,49 @@ export function getEventsScenesFlexItem(datasource: DataSourceInformation) {
|
||||
.setCustomFieldConfig('scaleDistribution', { type: ScaleDistribution.Linear })
|
||||
.setOption('legend', { showLegend: false, displayMode: LegendDisplayMode.Hidden })
|
||||
.setOption('tooltip', { mode: TooltipDisplayMode.Single })
|
||||
|
||||
.setNoValue('No events found')
|
||||
.build(),
|
||||
});
|
||||
}
|
||||
/*
|
||||
* This component shows a button to clear the filters.
|
||||
* It is shown when the filters are active.
|
||||
* props:
|
||||
* labelsFilterVariable: the textbox variable for filtering by labels
|
||||
* transitionsToFilterVariable: the custom variable for filtering by the current state
|
||||
* transitionsFromFilterVariable: the custom variable for filtering by the previous state
|
||||
*/
|
||||
|
||||
function ClearFilterButton({
|
||||
labelsFilterVariable,
|
||||
transitionsToFilterVariable,
|
||||
transitionsFromFilterVariable,
|
||||
}: {
|
||||
labelsFilterVariable: TextBoxVariable;
|
||||
transitionsToFilterVariable: CustomVariable;
|
||||
transitionsFromFilterVariable: CustomVariable;
|
||||
}) {
|
||||
// get the current values of the filters
|
||||
const valueInLabelsFilter = labelsFilterVariable.getValue();
|
||||
//todo: use parsePromQLStyleMatcherLooseSafe to validate the label filter and check the lenghtof the result
|
||||
const valueInTransitionsFilter = transitionsToFilterVariable.getValue();
|
||||
const valueInTransitionsFromFilter = transitionsFromFilterVariable.getValue();
|
||||
export class ClearFilterButtonScenesObject extends SceneObjectBase {
|
||||
public static Component = ClearFilterButtonObjectRenderer;
|
||||
|
||||
protected _variableDependency = new VariableDependencyConfig(this, {
|
||||
variableNames: [LABELS_FILTER, STATE_FILTER_FROM, STATE_FILTER_TO],
|
||||
});
|
||||
}
|
||||
|
||||
export function ClearFilterButtonObjectRenderer({ model }: SceneComponentProps<ClearFilterButtonScenesObject>) {
|
||||
// This make sure the component is re-rendered when the variables change
|
||||
model.useState();
|
||||
|
||||
const labelsFilter = sceneGraph.interpolate(model, '${LABELS_FILTER}');
|
||||
const stateTo = sceneGraph.interpolate(model, '${STATE_FILTER_TO}');
|
||||
const stateFrom = sceneGraph.interpolate(model, '${STATE_FILTER_FROM}');
|
||||
|
||||
// if no filter is active, return null
|
||||
if (
|
||||
!valueInLabelsFilter &&
|
||||
valueInTransitionsFilter === StateFilterValues.all &&
|
||||
valueInTransitionsFromFilter === StateFilterValues.all
|
||||
) {
|
||||
if (!labelsFilter && stateTo === StateFilterValues.all && stateFrom === StateFilterValues.all) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onClearFilter = () => {
|
||||
labelsFilterVariable.setValue('');
|
||||
transitionsToFilterVariable.changeValueTo(StateFilterValues.all);
|
||||
transitionsFromFilterVariable.changeValueTo(StateFilterValues.all);
|
||||
const labelsFiltersVariable = sceneGraph.lookupVariable(LABELS_FILTER, model);
|
||||
if (labelsFiltersVariable instanceof TextBoxVariable) {
|
||||
labelsFiltersVariable.setValue('');
|
||||
}
|
||||
|
||||
const stateToFilterVariable = sceneGraph.lookupVariable(STATE_FILTER_TO, model);
|
||||
if (stateToFilterVariable instanceof CustomVariable) {
|
||||
stateToFilterVariable.changeValueTo(StateFilterValues.all);
|
||||
}
|
||||
|
||||
const stateFromFilterVariable = sceneGraph.lookupVariable(STATE_FILTER_FROM, model);
|
||||
if (stateFromFilterVariable instanceof CustomVariable) {
|
||||
stateFromFilterVariable.changeValueTo(StateFilterValues.all);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip content="Clear filter">
|
||||
<Button variant={'secondary'} icon="times" onClick={onClearFilter}>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
import { DataQuery, DataQueryRequest, DataQueryResponse, TestDataSourceResponse } from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { RuntimeDataSource, sceneUtils } from '@grafana/scenes';
|
||||
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { dispatch } from 'app/store/store';
|
||||
@ -9,7 +10,7 @@ import { stateHistoryApi } from '../../../api/stateHistoryApi';
|
||||
import { DataSourceInformation } from '../../../home/Insights';
|
||||
|
||||
import { LIMIT_EVENTS } from './EventListSceneObject';
|
||||
import { getStateFilterFromInQueryParams, getStateFilterToInQueryParams, historyResultToDataFrame } from './utils';
|
||||
import { historyResultToDataFrame } from './utils';
|
||||
|
||||
const historyDataSourceUid = '__history_api_ds_uid__';
|
||||
const historyDataSourcePluginId = '__history_api_ds_pluginId__';
|
||||
@ -31,6 +32,12 @@ export function useRegisterHistoryRuntimeDataSource() {
|
||||
}, [ds]);
|
||||
}
|
||||
|
||||
interface HistoryAPIQuery extends DataQuery {
|
||||
labels?: string;
|
||||
stateFrom?: string;
|
||||
stateTo?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is a runtime datasource that fetches the events from the history api.
|
||||
* The events are grouped by alert instance and then converted to a DataFrame list.
|
||||
@ -38,23 +45,28 @@ export function useRegisterHistoryRuntimeDataSource() {
|
||||
* This allows us to filter the events by labels.
|
||||
* The result is a timeseries panel that shows the events for the selected time range and filtered by labels.
|
||||
*/
|
||||
class HistoryAPIDatasource extends RuntimeDataSource {
|
||||
class HistoryAPIDatasource extends RuntimeDataSource<HistoryAPIQuery> {
|
||||
constructor(pluginId: string, uid: string) {
|
||||
super(uid, pluginId);
|
||||
}
|
||||
|
||||
async query(request: DataQueryRequest<DataQuery>): Promise<DataQueryResponse> {
|
||||
async query(request: DataQueryRequest<HistoryAPIQuery>): Promise<DataQueryResponse> {
|
||||
const from = request.range.from.unix();
|
||||
const to = request.range.to.unix();
|
||||
// get the query from the request
|
||||
const query = request.targets[0]!;
|
||||
|
||||
// Get the labels and states filters from the URL
|
||||
const stateTo = getStateFilterToInQueryParams();
|
||||
const stateFrom = getStateFilterFromInQueryParams();
|
||||
const templateSrv = getTemplateSrv();
|
||||
|
||||
// we get the labels, stateTo and stateFrom from the query variables
|
||||
const labels = templateSrv.replace(query.labels ?? '', request.scopedVars);
|
||||
const stateTo = templateSrv.replace(query.stateTo ?? '', request.scopedVars);
|
||||
const stateFrom = templateSrv.replace(query.stateFrom ?? '', request.scopedVars);
|
||||
|
||||
const historyResult = await getHistory(from, to);
|
||||
|
||||
return {
|
||||
data: historyResultToDataFrame(historyResult, { stateTo, stateFrom }),
|
||||
data: historyResultToDataFrame(historyResult, { stateTo, stateFrom, labels }),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
SceneComponentProps,
|
||||
SceneObjectBase,
|
||||
TextBoxVariable,
|
||||
VariableDependencyConfig,
|
||||
VariableValue,
|
||||
sceneGraph,
|
||||
} from '@grafana/scenes';
|
||||
@ -495,48 +496,54 @@ export const getStyles = (theme: GrafanaTheme2) => {
|
||||
|
||||
export class HistoryEventsListObject extends SceneObjectBase {
|
||||
public static Component = HistoryEventsListObjectRenderer;
|
||||
public constructor() {
|
||||
super({});
|
||||
}
|
||||
|
||||
protected _variableDependency = new VariableDependencyConfig(this, {
|
||||
variableNames: [LABELS_FILTER, STATE_FILTER_FROM, STATE_FILTER_TO],
|
||||
});
|
||||
}
|
||||
|
||||
export type FilterType = 'label' | 'stateFrom' | 'stateTo';
|
||||
|
||||
export function HistoryEventsListObjectRenderer({ model }: SceneComponentProps<HistoryEventsListObject>) {
|
||||
const { value: timeRange } = sceneGraph.getTimeRange(model).useState(); // get time range from scene graph
|
||||
// eslint-disable-next-line
|
||||
const labelsFiltersVariable = sceneGraph.lookupVariable(LABELS_FILTER, model)! as TextBoxVariable;
|
||||
// eslint-disable-next-line
|
||||
const stateToFilterVariable = sceneGraph.lookupVariable(STATE_FILTER_TO, model)! as CustomVariable;
|
||||
// eslint-disable-next-line
|
||||
const stateFromFilterVariable = sceneGraph.lookupVariable(STATE_FILTER_FROM, model)! as CustomVariable;
|
||||
// This make sure the component is re-rendered when the variables change
|
||||
model.useState();
|
||||
|
||||
const valueInfilterTextBox: VariableValue = labelsFiltersVariable.getValue();
|
||||
const valueInStateToFilter = stateToFilterVariable.getValue();
|
||||
const valueInStateFromFilter = stateFromFilterVariable.getValue();
|
||||
const { value: timeRange } = sceneGraph.getTimeRange(model).useState(); // get time range from scene graph
|
||||
|
||||
const labelsFiltersVariable = sceneGraph.lookupVariable(LABELS_FILTER, model);
|
||||
const stateToFilterVariable = sceneGraph.lookupVariable(STATE_FILTER_TO, model);
|
||||
const stateFromFilterVariable = sceneGraph.lookupVariable(STATE_FILTER_FROM, model);
|
||||
|
||||
const addFilter = (key: string, value: string, type: FilterType) => {
|
||||
const newFilterToAdd = `${key}=${value}`;
|
||||
trackUseCentralHistoryFilterByClicking({ type, key, value });
|
||||
if (type === 'stateTo') {
|
||||
if (type === 'stateTo' && stateToFilterVariable instanceof CustomVariable) {
|
||||
stateToFilterVariable.changeValueTo(value);
|
||||
}
|
||||
if (type === 'stateFrom') {
|
||||
if (type === 'stateFrom' && stateFromFilterVariable instanceof CustomVariable) {
|
||||
stateFromFilterVariable.changeValueTo(value);
|
||||
}
|
||||
const finalFilter = combineMatcherStrings(valueInfilterTextBox.toString(), newFilterToAdd);
|
||||
if (type === 'label') {
|
||||
if (type === 'label' && labelsFiltersVariable instanceof TextBoxVariable) {
|
||||
const finalFilter = combineMatcherStrings(labelsFiltersVariable.state.value.toString(), newFilterToAdd);
|
||||
labelsFiltersVariable.setValue(finalFilter);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<HistoryEventsList
|
||||
timeRange={timeRange}
|
||||
valueInLabelFilter={valueInfilterTextBox}
|
||||
addFilter={addFilter}
|
||||
valueInStateToFilter={valueInStateToFilter}
|
||||
valueInStateFromFilter={valueInStateFromFilter}
|
||||
/>
|
||||
);
|
||||
if (
|
||||
stateToFilterVariable instanceof CustomVariable &&
|
||||
stateFromFilterVariable instanceof CustomVariable &&
|
||||
labelsFiltersVariable instanceof TextBoxVariable
|
||||
) {
|
||||
return (
|
||||
<HistoryEventsList
|
||||
timeRange={timeRange}
|
||||
valueInLabelFilter={labelsFiltersVariable.state.value}
|
||||
addFilter={addFilter}
|
||||
valueInStateToFilter={stateToFilterVariable.state.value}
|
||||
valueInStateFromFilter={stateFromFilterVariable.state.value}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ exports[`historyResultToDataFrame should decode 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`historyResultToDataFrame should decode and filter 1`] = `
|
||||
exports[`historyResultToDataFrame should decode and filter example1 1`] = `
|
||||
[
|
||||
{
|
||||
"fields": [
|
||||
@ -69,3 +69,34 @@ exports[`historyResultToDataFrame should decode and filter 1`] = `
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`historyResultToDataFrame should decode and filter example2 1`] = `
|
||||
[
|
||||
{
|
||||
"fields": [
|
||||
{
|
||||
"config": {
|
||||
"custom": {
|
||||
"fillOpacity": 100,
|
||||
},
|
||||
"displayName": "Time",
|
||||
},
|
||||
"name": "time",
|
||||
"type": "time",
|
||||
"values": [
|
||||
1727189670000,
|
||||
],
|
||||
},
|
||||
{
|
||||
"config": {},
|
||||
"name": "value",
|
||||
"type": "number",
|
||||
"values": [
|
||||
8,
|
||||
],
|
||||
},
|
||||
],
|
||||
"length": 1,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
@ -6,7 +6,18 @@ describe('historyResultToDataFrame', () => {
|
||||
expect(historyResultToDataFrame(fixtureData)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should decode and filter', () => {
|
||||
expect(historyResultToDataFrame(fixtureData, { stateFrom: 'Pending', stateTo: 'Alerting' })).toMatchSnapshot();
|
||||
it('should decode and filter example1', () => {
|
||||
expect(
|
||||
historyResultToDataFrame(fixtureData, {
|
||||
stateFrom: 'Pending',
|
||||
stateTo: 'Alerting',
|
||||
labels: "alertname: 'XSS attack vector'",
|
||||
})
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
it('should decode and filter example2', () => {
|
||||
expect(
|
||||
historyResultToDataFrame(fixtureData, { stateFrom: 'Normal', stateTo: 'NoData', labels: 'region: EMEA' })
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
@ -24,9 +24,16 @@ import { LABELS_FILTER, STATE_FILTER_FROM, STATE_FILTER_TO, StateFilterValues }
|
||||
const GROUPING_INTERVAL = 10 * 1000; // 10 seconds
|
||||
const QUERY_PARAM_PREFIX = 'var-'; // Prefix used by Grafana to sync variables in the URL
|
||||
|
||||
const emptyFilters = {
|
||||
interface HistoryFilters {
|
||||
stateTo: string;
|
||||
stateFrom: string;
|
||||
labels: string;
|
||||
}
|
||||
|
||||
const emptyFilters: HistoryFilters = {
|
||||
stateTo: 'all',
|
||||
stateFrom: 'all',
|
||||
labels: '',
|
||||
};
|
||||
|
||||
/*
|
||||
@ -75,7 +82,7 @@ export function historyResultToDataFrame({ data }: DataFrameJSON, filters = empt
|
||||
});
|
||||
|
||||
// Group DataFrames by time and filter by labels
|
||||
return groupDataFramesByTimeAndFilterByLabels(dataFrames);
|
||||
return groupDataFramesByTimeAndFilterByLabels(dataFrames, filters);
|
||||
}
|
||||
|
||||
// Scenes sync variables in the URL adding a prefix to the variable name.
|
||||
@ -98,9 +105,9 @@ export function getStateFilterFromInQueryParams() {
|
||||
* This function groups the data frames by time and filters them by labels.
|
||||
* The interval is set to 10 seconds.
|
||||
* */
|
||||
function groupDataFramesByTimeAndFilterByLabels(dataFrames: DataFrame[]): DataFrame[] {
|
||||
export function groupDataFramesByTimeAndFilterByLabels(dataFrames: DataFrame[], filters: HistoryFilters): DataFrame[] {
|
||||
// Filter data frames by labels. This is used to filter out the data frames that do not match the query.
|
||||
const labelsFilterValue = getLabelsFilterInQueryParams();
|
||||
const labelsFilterValue = filters.labels;
|
||||
const dataframesFiltered = dataFrames.filter((frame) => {
|
||||
const labels = JSON.parse(frame.name ?? ''); // in name we store the labels stringified
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user