3
0
mirror of https://github.com/grafana/grafana.git synced 2025-01-11 00:22:06 -06:00

Alerting: Improve performance ash page ()

* 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  ()

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:
Sonia Aguilar 2024-12-10 13:09:42 +01:00 committed by GitHub
parent 448d87157c
commit dd9638cade
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 221 additions and 161 deletions
public/app/features/alerting/unified/components/rules/central-state-history

View File

@ -1,14 +1,16 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { useEffect } from 'react'; import { useEffect, useMemo } from 'react';
import { GrafanaTheme2, VariableHide } from '@grafana/data'; import { GrafanaTheme2, VariableHide } from '@grafana/data';
import { import {
CustomVariable, CustomVariable,
EmbeddedScene, EmbeddedScene,
PanelBuilders, PanelBuilders,
SceneComponentProps,
SceneControlsSpacer, SceneControlsSpacer,
SceneFlexItem, SceneFlexItem,
SceneFlexLayout, SceneFlexLayout,
SceneObjectBase,
SceneQueryRunner, SceneQueryRunner,
SceneReactObject, SceneReactObject,
SceneRefreshPicker, SceneRefreshPicker,
@ -16,7 +18,9 @@ import {
SceneTimeRange, SceneTimeRange,
SceneVariableSet, SceneVariableSet,
TextBoxVariable, TextBoxVariable,
VariableDependencyConfig,
VariableValueSelectors, VariableValueSelectors,
sceneGraph,
useUrlSync, useUrlSync,
} from '@grafana/scenes'; } from '@grafana/scenes';
import { GraphDrawStyle, VisibilityMode } from '@grafana/schema/dist/esm/index'; import { GraphDrawStyle, VisibilityMode } from '@grafana/schema/dist/esm/index';
@ -36,14 +40,13 @@ import {
import { Trans } from 'app/core/internationalization'; import { Trans } from 'app/core/internationalization';
import { LogMessages, logInfo } from '../../../Analytics'; import { LogMessages, logInfo } from '../../../Analytics';
import { DataSourceInformation } from '../../../home/Insights';
import { alertStateHistoryDatasource, useRegisterHistoryRuntimeDataSource } from './CentralHistoryRuntimeDataSource'; import { alertStateHistoryDatasource, useRegisterHistoryRuntimeDataSource } from './CentralHistoryRuntimeDataSource';
import { HistoryEventsListObject } from './EventListSceneObject'; import { HistoryEventsListObject } from './EventListSceneObject';
export const LABELS_FILTER = 'labelsFilter'; export const LABELS_FILTER = 'LABELS_FILTER';
export const STATE_FILTER_TO = 'stateFilterTo'; export const STATE_FILTER_TO = 'STATE_FILTER_TO';
export const STATE_FILTER_FROM = 'stateFilterFrom'; export const STATE_FILTER_FROM = 'STATE_FILTER_FROM';
/** /**
* *
* This scene shows the history of the alert state changes. * This scene shows the history of the alert state changes.
@ -67,74 +70,72 @@ export const CentralAlertHistoryScene = () => {
logInfo(LogMessages.loadedCentralAlertStateHistory); 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. useRegisterHistoryRuntimeDataSource(); // register the runtime datasource for the history api.
const scene = new EmbeddedScene({ const scene = useMemo(() => {
controls: [ // create the variables for the filters
new SceneReactObject({ // textbox variable for filtering by labels
component: LabelFilter, const labelsFilterVariable = new TextBoxVariable({
}), name: LABELS_FILTER,
new SceneReactObject({ label: 'Labels: ',
component: FilterInfo, });
}),
new VariableValueSelectors({}), //custom variable for filtering by the current state
new SceneReactObject({ const transitionsToFilterVariable = new CustomVariable({
component: ClearFilterButton, name: STATE_FILTER_TO,
props: { value: StateFilterValues.all,
labelsFilterVariable, label: 'End state:',
transitionsToFilterVariable, hide: VariableHide.dontHide,
transitionsFromFilterVariable, query: `All : ${StateFilterValues.all}, To Firing : ${StateFilterValues.firing},To Normal : ${StateFilterValues.normal},To Pending : ${StateFilterValues.pending}`,
}, });
}),
new SceneControlsSpacer(), //custom variable for filtering by the previous state
new SceneTimePicker({}), const transitionsFromFilterVariable = new CustomVariable({
new SceneRefreshPicker({}), name: STATE_FILTER_FROM,
], value: StateFilterValues.all,
// use default time range as from 1 hour ago to now, as the limit of the history api is 5000 events, label: 'Start state:',
// and using a wider time range might lead to showing gaps in the events list and the chart. hide: VariableHide.dontHide,
$timeRange: new SceneTimeRange({ query: `All : ${StateFilterValues.all}, From Firing : ${StateFilterValues.firing},From Normal : ${StateFilterValues.normal},From Pending : ${StateFilterValues.pending}`,
from: 'now-1h', });
to: 'now',
}), return new EmbeddedScene({
$variables: new SceneVariableSet({ controls: [
variables: [labelsFilterVariable, transitionsFromFilterVariable, transitionsToFilterVariable], new SceneReactObject({
}), component: LabelFilter,
body: new SceneFlexLayout({
direction: 'column',
children: [
new SceneFlexItem({
ySizing: 'content',
body: getEventsSceneObject(alertStateHistoryDatasource),
}), }),
new SceneFlexItem({ new SceneReactObject({
body: new HistoryEventsListObject(), 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 // we need to call this to sync the url with the scene state
const isUrlSyncInitialized = useUrlSync(scene); const isUrlSyncInitialized = useUrlSync(scene);
@ -147,22 +148,11 @@ export const CentralAlertHistoryScene = () => {
/** /**
* Creates a SceneFlexItem with a timeseries panel that shows the events. * 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. * 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) { function getEventsSceneObject() {
return new EmbeddedScene({ return new SceneFlexLayout({
controls: [], direction: 'column',
body: new SceneFlexLayout({ children: [getEventsScenesFlexItem()],
direction: 'column',
children: [
new SceneFlexItem({
ySizing: 'content',
body: new SceneFlexLayout({
children: [getEventsScenesFlexItem(alertStateHistoryDataSource)],
}),
}),
],
}),
}); });
} }
@ -171,15 +161,15 @@ function getEventsSceneObject(alertStateHistoryDataSource: DataSourceInformation
* @param datasource the datasource information for the runtime datasource * @param datasource the datasource information for the runtime datasource
* @returns the SceneQueryRunner * @returns the SceneQueryRunner
*/ */
function getSceneQuery(datasource: DataSourceInformation) { function getQueryRunnerForAlertHistoryDataSource() {
const query = new SceneQueryRunner({ const query = new SceneQueryRunner({
datasource: datasource, datasource: alertStateHistoryDatasource,
queries: [ queries: [
{ {
refId: 'A', refId: 'A',
expr: '', labels: '${LABELS_FILTER}',
queryType: 'range', stateFrom: '${STATE_FILTER_FROM}',
step: '10s', 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. * 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. * 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({ return new SceneFlexItem({
minHeight: 300, minHeight: 300,
body: PanelBuilders.timeseries() body: PanelBuilders.timeseries()
@ -197,7 +187,7 @@ export function getEventsScenesFlexItem(datasource: DataSourceInformation) {
.setDescription( .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.' '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' }) .setColor({ mode: 'continuous-BlPu' })
.setCustomFieldConfig('fillOpacity', 100) .setCustomFieldConfig('fillOpacity', 100)
.setCustomFieldConfig('drawStyle', GraphDrawStyle.Bars) .setCustomFieldConfig('drawStyle', GraphDrawStyle.Bars)
@ -213,47 +203,49 @@ export function getEventsScenesFlexItem(datasource: DataSourceInformation) {
.setCustomFieldConfig('scaleDistribution', { type: ScaleDistribution.Linear }) .setCustomFieldConfig('scaleDistribution', { type: ScaleDistribution.Linear })
.setOption('legend', { showLegend: false, displayMode: LegendDisplayMode.Hidden }) .setOption('legend', { showLegend: false, displayMode: LegendDisplayMode.Hidden })
.setOption('tooltip', { mode: TooltipDisplayMode.Single }) .setOption('tooltip', { mode: TooltipDisplayMode.Single })
.setNoValue('No events found') .setNoValue('No events found')
.build(), .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({ export class ClearFilterButtonScenesObject extends SceneObjectBase {
labelsFilterVariable, public static Component = ClearFilterButtonObjectRenderer;
transitionsToFilterVariable,
transitionsFromFilterVariable, protected _variableDependency = new VariableDependencyConfig(this, {
}: { variableNames: [LABELS_FILTER, STATE_FILTER_FROM, STATE_FILTER_TO],
labelsFilterVariable: TextBoxVariable; });
transitionsToFilterVariable: CustomVariable; }
transitionsFromFilterVariable: CustomVariable;
}) { export function ClearFilterButtonObjectRenderer({ model }: SceneComponentProps<ClearFilterButtonScenesObject>) {
// get the current values of the filters // This make sure the component is re-rendered when the variables change
const valueInLabelsFilter = labelsFilterVariable.getValue(); model.useState();
//todo: use parsePromQLStyleMatcherLooseSafe to validate the label filter and check the lenghtof the result
const valueInTransitionsFilter = transitionsToFilterVariable.getValue(); const labelsFilter = sceneGraph.interpolate(model, '${LABELS_FILTER}');
const valueInTransitionsFromFilter = transitionsFromFilterVariable.getValue(); const stateTo = sceneGraph.interpolate(model, '${STATE_FILTER_TO}');
const stateFrom = sceneGraph.interpolate(model, '${STATE_FILTER_FROM}');
// if no filter is active, return null // if no filter is active, return null
if ( if (!labelsFilter && stateTo === StateFilterValues.all && stateFrom === StateFilterValues.all) {
!valueInLabelsFilter &&
valueInTransitionsFilter === StateFilterValues.all &&
valueInTransitionsFromFilter === StateFilterValues.all
) {
return null; return null;
} }
const onClearFilter = () => { const onClearFilter = () => {
labelsFilterVariable.setValue(''); const labelsFiltersVariable = sceneGraph.lookupVariable(LABELS_FILTER, model);
transitionsToFilterVariable.changeValueTo(StateFilterValues.all); if (labelsFiltersVariable instanceof TextBoxVariable) {
transitionsFromFilterVariable.changeValueTo(StateFilterValues.all); 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 ( return (
<Tooltip content="Clear filter"> <Tooltip content="Clear filter">
<Button variant={'secondary'} icon="times" onClick={onClearFilter}> <Button variant={'secondary'} icon="times" onClick={onClearFilter}>

View File

@ -1,6 +1,7 @@
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { DataQuery, DataQueryRequest, DataQueryResponse, TestDataSourceResponse } from '@grafana/data'; import { DataQuery, DataQueryRequest, DataQueryResponse, TestDataSourceResponse } from '@grafana/data';
import { getTemplateSrv } from '@grafana/runtime';
import { RuntimeDataSource, sceneUtils } from '@grafana/scenes'; import { RuntimeDataSource, sceneUtils } from '@grafana/scenes';
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { dispatch } from 'app/store/store'; import { dispatch } from 'app/store/store';
@ -9,7 +10,7 @@ import { stateHistoryApi } from '../../../api/stateHistoryApi';
import { DataSourceInformation } from '../../../home/Insights'; import { DataSourceInformation } from '../../../home/Insights';
import { LIMIT_EVENTS } from './EventListSceneObject'; import { LIMIT_EVENTS } from './EventListSceneObject';
import { getStateFilterFromInQueryParams, getStateFilterToInQueryParams, historyResultToDataFrame } from './utils'; import { historyResultToDataFrame } from './utils';
const historyDataSourceUid = '__history_api_ds_uid__'; const historyDataSourceUid = '__history_api_ds_uid__';
const historyDataSourcePluginId = '__history_api_ds_pluginId__'; const historyDataSourcePluginId = '__history_api_ds_pluginId__';
@ -31,6 +32,12 @@ export function useRegisterHistoryRuntimeDataSource() {
}, [ds]); }, [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. * 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. * 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. * 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. * 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) { constructor(pluginId: string, uid: string) {
super(uid, pluginId); super(uid, pluginId);
} }
async query(request: DataQueryRequest<DataQuery>): Promise<DataQueryResponse> { async query(request: DataQueryRequest<HistoryAPIQuery>): Promise<DataQueryResponse> {
const from = request.range.from.unix(); const from = request.range.from.unix();
const to = request.range.to.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 templateSrv = getTemplateSrv();
const stateTo = getStateFilterToInQueryParams();
const stateFrom = getStateFilterFromInQueryParams(); // 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); const historyResult = await getHistory(from, to);
return { return {
data: historyResultToDataFrame(historyResult, { stateTo, stateFrom }), data: historyResultToDataFrame(historyResult, { stateTo, stateFrom, labels }),
}; };
} }

View File

@ -9,6 +9,7 @@ import {
SceneComponentProps, SceneComponentProps,
SceneObjectBase, SceneObjectBase,
TextBoxVariable, TextBoxVariable,
VariableDependencyConfig,
VariableValue, VariableValue,
sceneGraph, sceneGraph,
} from '@grafana/scenes'; } from '@grafana/scenes';
@ -495,48 +496,54 @@ export const getStyles = (theme: GrafanaTheme2) => {
export class HistoryEventsListObject extends SceneObjectBase { export class HistoryEventsListObject extends SceneObjectBase {
public static Component = HistoryEventsListObjectRenderer; 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 type FilterType = 'label' | 'stateFrom' | 'stateTo';
export function HistoryEventsListObjectRenderer({ model }: SceneComponentProps<HistoryEventsListObject>) { export function HistoryEventsListObjectRenderer({ model }: SceneComponentProps<HistoryEventsListObject>) {
const { value: timeRange } = sceneGraph.getTimeRange(model).useState(); // get time range from scene graph // This make sure the component is re-rendered when the variables change
// eslint-disable-next-line model.useState();
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;
const valueInfilterTextBox: VariableValue = labelsFiltersVariable.getValue(); const { value: timeRange } = sceneGraph.getTimeRange(model).useState(); // get time range from scene graph
const valueInStateToFilter = stateToFilterVariable.getValue();
const valueInStateFromFilter = stateFromFilterVariable.getValue(); 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 addFilter = (key: string, value: string, type: FilterType) => {
const newFilterToAdd = `${key}=${value}`; const newFilterToAdd = `${key}=${value}`;
trackUseCentralHistoryFilterByClicking({ type, key, value }); trackUseCentralHistoryFilterByClicking({ type, key, value });
if (type === 'stateTo') { if (type === 'stateTo' && stateToFilterVariable instanceof CustomVariable) {
stateToFilterVariable.changeValueTo(value); stateToFilterVariable.changeValueTo(value);
} }
if (type === 'stateFrom') { if (type === 'stateFrom' && stateFromFilterVariable instanceof CustomVariable) {
stateFromFilterVariable.changeValueTo(value); stateFromFilterVariable.changeValueTo(value);
} }
const finalFilter = combineMatcherStrings(valueInfilterTextBox.toString(), newFilterToAdd); if (type === 'label' && labelsFiltersVariable instanceof TextBoxVariable) {
if (type === 'label') { const finalFilter = combineMatcherStrings(labelsFiltersVariable.state.value.toString(), newFilterToAdd);
labelsFiltersVariable.setValue(finalFilter); labelsFiltersVariable.setValue(finalFilter);
} }
}; };
return ( if (
<HistoryEventsList stateToFilterVariable instanceof CustomVariable &&
timeRange={timeRange} stateFromFilterVariable instanceof CustomVariable &&
valueInLabelFilter={valueInfilterTextBox} labelsFiltersVariable instanceof TextBoxVariable
addFilter={addFilter} ) {
valueInStateToFilter={valueInStateToFilter} return (
valueInStateFromFilter={valueInStateFromFilter} <HistoryEventsList
/> timeRange={timeRange}
); valueInLabelFilter={labelsFiltersVariable.state.value}
addFilter={addFilter}
valueInStateToFilter={stateToFilterVariable.state.value}
valueInStateFromFilter={stateFromFilterVariable.state.value}
/>
);
} else {
return null;
}
} }

View File

@ -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": [ "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,
},
]
`;

View File

@ -6,7 +6,18 @@ describe('historyResultToDataFrame', () => {
expect(historyResultToDataFrame(fixtureData)).toMatchSnapshot(); expect(historyResultToDataFrame(fixtureData)).toMatchSnapshot();
}); });
it('should decode and filter', () => { it('should decode and filter example1', () => {
expect(historyResultToDataFrame(fixtureData, { stateFrom: 'Pending', stateTo: 'Alerting' })).toMatchSnapshot(); 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();
}); });
}); });

View File

@ -24,9 +24,16 @@ import { LABELS_FILTER, STATE_FILTER_FROM, STATE_FILTER_TO, StateFilterValues }
const GROUPING_INTERVAL = 10 * 1000; // 10 seconds const GROUPING_INTERVAL = 10 * 1000; // 10 seconds
const QUERY_PARAM_PREFIX = 'var-'; // Prefix used by Grafana to sync variables in the URL 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', stateTo: 'all',
stateFrom: 'all', stateFrom: 'all',
labels: '',
}; };
/* /*
@ -75,7 +82,7 @@ export function historyResultToDataFrame({ data }: DataFrameJSON, filters = empt
}); });
// Group DataFrames by time and filter by labels // 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. // 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. * This function groups the data frames by time and filters them by labels.
* The interval is set to 10 seconds. * 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. // 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 dataframesFiltered = dataFrames.filter((frame) => {
const labels = JSON.parse(frame.name ?? ''); // in name we store the labels stringified const labels = JSON.parse(frame.name ?? ''); // in name we store the labels stringified