Skip to content

Commit

Permalink
Regex labels: support queries from explore (#1010)
Browse files Browse the repository at this point in the history
* feat: support label regex operations in queries from explore
  • Loading branch information
gtk-grafana authored Jan 27, 2025
1 parent 3fb021e commit a486e7f
Show file tree
Hide file tree
Showing 17 changed files with 923 additions and 262 deletions.
38 changes: 12 additions & 26 deletions src/Components/IndexScene/IndexScene.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import { AdHocVariableFilter, AppEvents, AppPluginMeta, rangeUtil, SelectableValue } from '@grafana/data';
import { AdHocVariableFilter, AppEvents, AppPluginMeta, rangeUtil } from '@grafana/data';
import {
AdHocFiltersVariable,
CustomVariable,
Expand Down Expand Up @@ -82,11 +82,19 @@ import { lokiRegularEscape } from '../../services/fields';
import { logger } from '../../services/logger';
import { getLabelsTagKeysProvider } from '../../services/TagKeysProviders';
import { AdHocFilterWithLabels, getLokiDatasource } from '../../services/scenes';
import { FilterOp, LineFilterOp } from '../../services/filterTypes';
import { FilterOp } from '../../services/filterTypes';
import { ShowLogsButtonScene } from './ShowLogsButtonScene';
import { CustomVariableValueSelectors } from './CustomVariableValueSelectors';
import { getCopiedTimeRange, PasteTimeEvent, setupKeyboardShortcuts } from '../../services/keyboardShortcuts';
import { LokiDatasource } from '../../services/lokiQuery';
import {
includeOperators,
isOperatorInclusive,
lineFilterOperators,
numericOperatorArray,
numericOperators,
operators,
} from '../../services/operators';

export const showLogsButtonSceneKey = 'showLogsButtonScene';
export interface AppliedPattern {
Expand Down Expand Up @@ -239,7 +247,7 @@ export class IndexScene extends SceneObjectBase<IndexSceneState> {
const wip = labelsVar.state._wip;
if (
wip &&
labelsVar.state.filters.some((filter) => filter.key === wip.key && filter.operator === FilterOp.Equal)
labelsVar.state.filters.some((filter) => filter.key === wip.key && isOperatorInclusive(filter.operator))
) {
return includeOperators;
}
Expand Down Expand Up @@ -437,36 +445,14 @@ function getContentScene(drillDownLabel?: string) {
drillDownLabel,
});
}
const operators = [FilterOp.Equal, FilterOp.NotEqual].map<SelectableValue<string>>((value) => ({
label: value,
value,
}));

const includeOperators = [FilterOp.Equal].map<SelectableValue<string>>((value) => ({
label: value,
value,
}));

export const numericOperatorArray = [FilterOp.gt, FilterOp.gte, FilterOp.lt, FilterOp.lte];

const numericOperators = numericOperatorArray.map<SelectableValue<string>>((value) => ({
label: value,
value,
}));

const lineFilterOperators: SelectableValue[] = [
{ label: 'match', value: LineFilterOp.match },
{ label: 'negativeMatch', value: LineFilterOp.negativeMatch },
{ label: 'regex', value: LineFilterOp.regex },
{ label: 'negativeRegex', value: LineFilterOp.negativeRegex },
];

function getVariableSet(initialDatasourceUid: string, initialFilters?: AdHocVariableFilter[]) {
const labelVariable = new AdHocFiltersVariable({
name: VAR_LABELS,
datasource: EXPLORATION_DS,
layout: 'combobox',
label: 'Labels',
allowCustomValue: true,
filters: initialFilters ?? [],
expressionBuilder: renderLogQLLabelFilters,
hide: VariableHide.dontHide,
Expand Down
8 changes: 4 additions & 4 deletions src/Components/IndexScene/ShowLogsButtonScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { GrafanaTheme2 } from '@grafana/data';
import { css } from '@emotion/css';
import { navigateToInitialPageAfterServiceSelection } from '../../services/navigate';
import { getLabelsVariable } from '../../services/variableGetters';
import { FilterOp } from '../../services/filterTypes';
import { testIds } from '../../services/testIds';
import { isOperatorInclusive } from '../../services/operators';

export interface ShowLogsButtonSceneState extends SceneObjectState {
disabled?: boolean;
Expand All @@ -23,13 +23,13 @@ export class ShowLogsButtonScene extends SceneObjectBase<ShowLogsButtonSceneStat

onActivate() {
const labelsVar = getLabelsVariable(this);
const hasPositiveFilter = labelsVar.state.filters.some((f) => f.operator === FilterOp.Equal);
const hasPositiveFilter = labelsVar.state.filters.some((f) => isOperatorInclusive(f.operator));
this.setState({
disabled: !hasPositiveFilter,
});

labelsVar.subscribeToState((newState) => {
const hasPositiveFilter = newState.filters.some((f) => f.operator === FilterOp.Equal);
const hasPositiveFilter = newState.filters.some((f) => isOperatorInclusive(f.operator));
this.setState({
disabled: !hasPositiveFilter,
});
Expand All @@ -38,7 +38,7 @@ export class ShowLogsButtonScene extends SceneObjectBase<ShowLogsButtonSceneStat

onClick = () => {
const labelsVar = getLabelsVariable(this);
const positiveFilter = labelsVar.state.filters.find((f) => f.operator === FilterOp.Equal);
const positiveFilter = labelsVar.state.filters.find((f) => isOperatorInclusive(f.operator));

if (positiveFilter) {
navigateToInitialPageAfterServiceSelection(positiveFilter.key, positiveFilter.value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ export class AddToFiltersButton extends SceneObjectBase<AddToFiltersButtonState>
return { isIncluded: false, isExcluded: false };
}

// @todo support regex operators?
return {
isIncluded: filterInSelectedFilters.operator === FilterOp.Equal,
isExcluded: filterInSelectedFilters.operator === FilterOp.NotEqual,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export class SelectLabelActionScene extends SceneObjectBase<SelectLabelActionSce
const popoverRef = useRef<HTMLButtonElement>(null);
const filterButtonDisabled =
fieldType === ValueSlugs.label &&
// @todo support regex operators?
variable.state.filters.filter((f) => f.key !== labelName && f.operator === FilterOp.Equal).length === 0;

const isIncluded = existingFilter?.operator === FilterOp.NotEqual && fieldValue.value === EMPTY_VARIABLE_VALUE;
Expand Down
7 changes: 5 additions & 2 deletions src/Components/ServiceScene/ServiceScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
import { replaceSlash } from '../../services/extensions/links';
import { ShowLogsButtonScene } from '../IndexScene/ShowLogsButtonScene';
import { migrateLineFilterV1 } from '../../services/migrations';
import { isOperatorInclusive } from '../../services/operators';

export const LOGS_PANEL_QUERY_REFID = 'logsPanelQuery';
export const LOGS_COUNT_QUERY_REFID = 'logsCountQuery';
Expand Down Expand Up @@ -182,10 +183,12 @@ export class ServiceScene extends SceneObjectBase<ServiceSceneState> {
// The "primary" label used in the URL is no longer active, pick a new one
if (
!newState.filters.some(
(f) => f.key === labelName && f.operator === '=' && replaceSlash(f.value) === labelValue
(f) => f.key === labelName && isOperatorInclusive(f.operator) && replaceSlash(f.value) === labelValue
)
) {
const newPrimaryLabel = newState.filters.find((f) => f.operator === '=' && f.value !== EMPTY_VARIABLE_VALUE);
const newPrimaryLabel = newState.filters.find(
(f) => isOperatorInclusive(f.operator) && f.value !== EMPTY_VARIABLE_VALUE
);
if (newPrimaryLabel) {
indexScene.setState({
routeMatch: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class AddLabelToFiltersHeaderActionScene extends SceneObjectBase<AddLabel
return { included: false };
}

// @todo support regex operator
return {
included: filterInSelectedFilters.operator === FilterOp.Equal,
};
Expand Down
1 change: 1 addition & 0 deletions src/services/TagKeysProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export async function getLabelsTagKeysProvider(variable: AdHocFiltersVariable):
filters,
};

// Do we want to only have regex operations?
const tagKeys = await datasource.getTagKeys(options);
const result: MetricFindValue[] = Array.isArray(tagKeys) ? tagKeys : [];
const filteredResult = result.filter((key) => !LABELS_TO_REMOVE.includes(key.text));
Expand Down
19 changes: 16 additions & 3 deletions src/services/TagValuesProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { isArray } from 'lodash';
import { joinTagFilters } from './query';
import { FilterOp } from './filterTypes';
import { getFavoriteLabelValuesFromStorage } from './store';
import { isOperatorInclusive, isOperatorRegex } from './operators';

type FetchDetectedLabelValuesOptions = {
expr?: string;
Expand Down Expand Up @@ -118,7 +119,14 @@ export async function getLabelsTagValuesProvider(

if (datasource && datasource.getTagValues) {
// Filter out other values for this key so users can include other values for this label
const filters = joinTagFilters(variable).filter((f) => !(filter.operator === '=' && f.key === filter.key));
let filters = joinTagFilters(variable).filter(
(f) => !(isOperatorInclusive(filter.operator) && f.key === filter.key)
);

// If there aren't any inclusive filters, we need to ignore the exclusive ones as well, or Loki will throw an error
if (!filters.some((filter) => isOperatorInclusive(filter.operator))) {
filters = [];
}

const options: DataSourceGetTagValuesOptions<LokiQuery> = {
key: filter.key,
Expand All @@ -132,8 +140,13 @@ export async function getLabelsTagValuesProvider(
return !variable.state.filters
.filter((f) => f.key === filter.key)
.some((f) => {
// If true, the results should be filtered out
return f.operator === FilterOp.Equal && f.value === result.text;
if (isOperatorRegex(f.operator)) {
const values = f.value.split('|');
return values.some((value) => value === result.text);
} else {
// If true, the results should be filtered out
return f.operator === FilterOp.Equal && f.value === result.text;
}
});
});
const favoriteValuesArray = getFavoriteLabelValuesFromStorage(
Expand Down
Loading

0 comments on commit a486e7f

Please sign in to comment.