From 963130d0303e78c6296ac789951a9545e9480fb4 Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Tue, 9 Jul 2024 11:04:49 +0200 Subject: [PATCH] Revert "Merge pull request #518 from grafana/svennergr/use-detected-fields-api" (#534) This reverts commit a38f364edd58d7a658ca51cfd1778ccc4dd63994, reversing changes made to 4bfeb6e175d84988b08b143337919636abe2f581. --- .../Breakdowns/FieldsBreakdownScene.tsx | 145 ++++++++---------- src/Components/ServiceScene/ServiceScene.tsx | 77 +++++----- src/models/DetectedField.ts | 17 -- src/services/variables.ts | 1 - 4 files changed, 106 insertions(+), 134 deletions(-) delete mode 100644 src/models/DetectedField.ts diff --git a/src/Components/ServiceScene/Breakdowns/FieldsBreakdownScene.tsx b/src/Components/ServiceScene/Breakdowns/FieldsBreakdownScene.tsx index e75edf845..bdfd9a07c 100644 --- a/src/Components/ServiceScene/Breakdowns/FieldsBreakdownScene.tsx +++ b/src/Components/ServiceScene/Breakdowns/FieldsBreakdownScene.tsx @@ -3,7 +3,6 @@ import React from 'react'; import { GrafanaTheme2, ReducerID, SelectableValue } from '@grafana/data'; import { - AdHocFiltersVariable, CustomVariable, PanelBuilders, SceneComponentProps, @@ -24,35 +23,35 @@ import { Alert, Button, DrawStyle, LoadingPlaceholder, StackingMode, useStyles2 import { reportAppInteraction, USER_EVENTS_ACTIONS, USER_EVENTS_PAGES } from 'services/analytics'; import { getFilterBreakdownValueScene } from 'services/fields'; import { getQueryRunner, setLeverColorOverrides } from 'services/panel'; -import { buildLokiQuery, renderLogQLStreamSelector } from 'services/query'; -import { getSortByPreference } from 'services/store'; +import { buildLokiQuery } from 'services/query'; import { ALL_VARIABLE_VALUE, - LOG_EXPR_WITHOUT_STREAM_SELECTOR, - VAR_FIELDS, + LOG_STREAM_SELECTOR_EXPR, VAR_FIELD_GROUP_BY, + VAR_FIELDS, VAR_LABELS, } from 'services/variables'; import { ServiceScene } from '../ServiceScene'; -import { BreakdownSearchScene, getLabelValue } from './BreakdownSearchScene'; import { ByFrameRepeater } from './ByFrameRepeater'; import { FieldSelector } from './FieldSelector'; import { LayoutSwitcher } from './LayoutSwitcher'; -import { SortByScene, SortCriteriaChanged } from './SortByScene'; import { StatusWrapper } from './StatusWrapper'; -import { DetectedField } from 'models/DetectedField'; +import { BreakdownSearchScene, getLabelValue } from './BreakdownSearchScene'; +import { SortByScene, SortCriteriaChanged } from './SortByScene'; +import { getSortByPreference } from 'services/store'; + export interface FieldsBreakdownSceneState extends SceneObjectState { body?: SceneObject; search: BreakdownSearchScene; sort: SortByScene; - fields: Array>; + fields: Array>; - fieldLabel?: string; + value?: string; loading?: boolean; error?: string; blockingMessage?: string; - changeFields?: (n: DetectedField[]) => void; + changeFields?: (n: string[]) => void; } export class FieldsBreakdownScene extends SceneObjectBase { @@ -104,17 +103,17 @@ export class FieldsBreakdownScene extends SceneObjectBase ({ - label: f.label, + { label: 'All', value: ALL_VARIABLE_VALUE }, + ...(logsScene.state.fields ?? []).map((f) => ({ + label: f, value: f, })), ], - loading: serviceScene.state.loading, + loading: logsScene.state.loading, }); this.updateBody(); @@ -131,16 +130,16 @@ export class FieldsBreakdownScene extends SceneObjectBase f.value?.label !== field); + const fields = this.state.fields.filter((f) => f.value !== field); this.setState({ fields }); - this.state.changeFields?.(fields.filter((f) => f.value?.type !== ALL_VARIABLE_VALUE).map((f) => f.value!)); + this.state.changeFields?.(fields.filter((f) => f.value !== ALL_VARIABLE_VALUE).map((f) => f.value!)); } private handleSortByChange = (event: SortCriteriaChanged) => { @@ -159,26 +158,18 @@ export class FieldsBreakdownScene extends SceneObjectBase = { - fieldLabel: fieldLabelVariable.getValueText(), + value: String(variable.state.value), blockingMessage: undefined, }; if (this.state.loading === false && this.state.fields.length === 1) { stateUpdate.body = this.buildEmptyLayout(); } else { - stateUpdate.body = - fieldLabelVariable.hasAllValue() || !detectedField || detectedField.type === ALL_VARIABLE_VALUE - ? this.buildFieldsLayout(this.state.fields, streamSelector) - : buildValuesLayout(detectedField, streamSelector); + stateUpdate.body = variable.hasAllValue() + ? this.buildFieldsLayout(this.state.fields) + : buildValuesLayout(variable); } this.setState(stateUpdate); @@ -212,25 +203,25 @@ export class FieldsBreakdownScene extends SceneObjectBase>, streamSelector: string) { + private buildFieldsLayout(options: Array>) { const children: SceneFlexItemLike[] = []; - for (const option of detectedFields) { - const { value: detectedField } = option; - if (detectedField?.type === ALL_VARIABLE_VALUE || !detectedField) { + for (const option of options) { + const { value: optionValue } = option; + if (optionValue === ALL_VARIABLE_VALUE || !optionValue) { continue; } - const query = buildLokiQuery(getExpr(detectedField, streamSelector), { - legendFormat: `{{${detectedField.label}}}`, - refId: detectedField.label, + const query = buildLokiQuery(getExpr(optionValue), { + legendFormat: `{{${optionValue}}}`, + refId: optionValue, }); const queryRunner = getQueryRunner(query); - let body = PanelBuilders.timeseries().setTitle(detectedField.label).setData(queryRunner); + let body = PanelBuilders.timeseries().setTitle(optionValue).setData(queryRunner); - if (!isAvgFieldType(detectedField.type)) { + if (!isAvgField(optionValue)) { body = body - .setHeaderActions(new SelectLabelAction({ field: detectedField })) + .setHeaderActions(new SelectLabelAction({ labelName: String(optionValue) })) .setCustomFieldConfig('stacking', { mode: StackingMode.Normal }) .setCustomFieldConfig('fillOpacity', 100) .setCustomFieldConfig('lineWidth', 0) @@ -245,7 +236,7 @@ export class FieldsBreakdownScene extends SceneObjectBase { if (result.data.errors && result.data.errors.length > 0) { const val = result.data.errors[0].refId!; - this.hideFieldByLabel(val); + this.hideField(val); gridItem.setState({ isHidden: true }); } }); @@ -276,48 +267,42 @@ export class FieldsBreakdownScene extends SceneObjectBase { - if (!detectedField) { + public onFieldSelectorChange = (value?: string) => { + if (!value) { return; } - const fieldLabelVariable = this.getVariable(); + const variable = this.getVariable(); reportAppInteraction( USER_EVENTS_PAGES.service_details, USER_EVENTS_ACTIONS.service_details.select_field_in_breakdown_clicked, { - field: detectedField.label, - previousField: fieldLabelVariable.getValueText(), + field: value, + previousField: variable.getValueText(), view: 'fields', } ); - fieldLabelVariable.changeValueTo(detectedField.label); + variable.changeValueTo(value); }; public static Component = ({ model }: SceneComponentProps) => { - const { fields, body, loading, fieldLabel, blockingMessage, search, sort } = model.useState(); + const { fields, body, loading, value, blockingMessage, search, sort } = model.useState(); const styles = useStyles2(getStyles); - const detectedField = fieldLabel ? getFieldByLabel(fields, fieldLabel) : undefined; return (
{body instanceof LayoutSwitcher && } - {!loading && detectedField?.type !== ALL_VARIABLE_VALUE && ( + {!loading && value !== ALL_VARIABLE_VALUE && ( <> )} {!loading && fields.length > 1 && ( - - label="Field" - options={fields} - value={detectedField} - onChange={model.onFieldSelectorChange} - /> + )}
{body && }
@@ -333,10 +318,6 @@ const emptyStateStyles = { }), }; -function getFieldByLabel(fields: Array>, fieldLabel: string) { - return fields.find((f) => f?.label === fieldLabel)?.value; -} - function getStyles(theme: GrafanaTheme2) { return { container: css({ @@ -361,21 +342,28 @@ function getStyles(theme: GrafanaTheme2) { }; } -function isAvgFieldType(fieldType: string) { - return ['duration', 'bytes'].includes(fieldType); +const avgFields = ['duration', 'count', 'total', 'bytes']; + +function isAvgField(field: string) { + return avgFields.includes(field); } -function getExpr(field: DetectedField, streamSelector: string) { - if (isAvgFieldType(field.type)) { - return `avg_over_time(${streamSelector} ${LOG_EXPR_WITHOUT_STREAM_SELECTOR} | ${field.parsers[0]} | ${field.label}!="" | unwrap ${field.type}(${field.label}) [$__auto]) by ()`; +function getExpr(field: string) { + if (isAvgField(field)) { + return ( + `avg_over_time(${LOG_STREAM_SELECTOR_EXPR} | unwrap ` + + (field === 'duration' ? `duration` : field === 'bytes' ? `bytes` : ``) + + `(${field}) [$__auto]) by ()` + ); } - return `sum by (${field.label}) (count_over_time(${streamSelector} ${LOG_EXPR_WITHOUT_STREAM_SELECTOR} | ${field.parsers[0]} | drop __error__ | ${field.label}!="" [$__auto]))`; + return `sum by (${field}) (count_over_time(${LOG_STREAM_SELECTOR_EXPR} | drop __error__ | ${field}!="" [$__auto]))`; } const GRID_TEMPLATE_COLUMNS = 'repeat(auto-fit, minmax(400px, 1fr))'; -function buildValuesLayout(field: DetectedField, streamSelector: string) { - const query = buildLokiQuery(getExpr(field, streamSelector), { legendFormat: `{{${field.label}}}` }); +function buildValuesLayout(variable: CustomVariable) { + const tagKey = variable.getValueText(); + const query = buildLokiQuery(getExpr(tagKey), { legendFormat: `{{${tagKey}}}` }); const { sortBy, direction } = getSortByPreference('fields', ReducerID.stdDev, 'desc'); @@ -393,7 +381,7 @@ function buildValuesLayout(field: DetectedField, streamSelector: string) { children: [ new SceneFlexItem({ minHeight: 300, - body: PanelBuilders.timeseries().setTitle(field.label).build(), + body: PanelBuilders.timeseries().setTitle(variable.getValueText()).build(), }), ], }), @@ -443,7 +431,7 @@ function buildValuesLayout(field: DetectedField, streamSelector: string) { }); } -export function buildFieldsBreakdownActionScene(changeFieldNumber: (n: DetectedField[]) => void) { +export function buildFieldsBreakdownActionScene(changeFieldNumber: (n: string[]) => void) { return new SceneFlexLayout({ children: [ new SceneFlexItem({ @@ -454,21 +442,16 @@ export function buildFieldsBreakdownActionScene(changeFieldNumber: (n: DetectedF } interface SelectLabelActionState extends SceneObjectState { - field: DetectedField; + labelName: string; } export class SelectLabelAction extends SceneObjectBase { public onClick = () => { - getFieldsBreakdownSceneFor(this).onFieldSelectorChange(this.state.field); + getFieldsBreakdownSceneFor(this).onFieldSelectorChange(this.state.labelName); }; public static Component = ({ model }: SceneComponentProps) => { return ( - ); diff --git a/src/Components/ServiceScene/ServiceScene.tsx b/src/Components/ServiceScene/ServiceScene.tsx index 3d604d487..3888da8a1 100644 --- a/src/Components/ServiceScene/ServiceScene.tsx +++ b/src/Components/ServiceScene/ServiceScene.tsx @@ -1,9 +1,10 @@ import { css } from '@emotion/css'; import React from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, LoadingState } from '@grafana/data'; import { AdHocFiltersVariable, + CustomVariable, SceneComponentProps, SceneFlexItem, SceneFlexLayout, @@ -17,13 +18,11 @@ import { import { Box, Stack, Tab, TabsBar, useStyles2 } from '@grafana/ui'; import { Unsubscribable } from 'rxjs'; import { reportAppInteraction, USER_EVENTS_ACTIONS, USER_EVENTS_PAGES } from 'services/analytics'; -import { DetectedLabelsResponse } from 'services/fields'; -import { sortLabelsByCardinality } from 'services/filters'; +import { DetectedLabelsResponse, extractParserAndFieldsFromDataFrame } from 'services/fields'; import { getQueryRunner } from 'services/panel'; import { buildLokiQuery, renderLogQLStreamSelector } from 'services/query'; import { getSlug, navigateToBreakdown, navigateToIndex, PLUGIN_ID, PageSlugs } from 'services/routing'; import { getExplorationFor, getLokiDatasource } from 'services/scenes'; -import { testIds } from 'services/testIds'; import { ALL_VARIABLE_VALUE, LEVEL_VARIABLE_VALUE, @@ -31,6 +30,7 @@ import { VAR_DATASOURCE, VAR_FIELDS, VAR_LABELS, + VAR_LOGS_FORMAT, VAR_PATTERNS, } from 'services/variables'; import { buildFieldsBreakdownActionScene } from './Breakdowns/FieldsBreakdownScene'; @@ -38,8 +38,9 @@ import { buildLabelBreakdownActionScene } from './Breakdowns/LabelBreakdownScene import { buildPatternsScene } from './Breakdowns/Patterns/PatternsBreakdownScene'; import { GoToExploreButton } from './GoToExploreButton'; import { buildLogsListScene } from './LogsListScene'; +import { testIds } from 'services/testIds'; +import { sortLabelsByCardinality } from 'services/filters'; import { SERVICE_NAME } from 'Components/ServiceSelectionScene/ServiceSelectionScene'; -import { DetectedField } from 'models/DetectedField'; export interface LokiPattern { pattern: string; @@ -50,7 +51,7 @@ interface BreakdownViewDefinition { displayName: string; value: PageSlugs; testId: string; - getScene: (changeFields: (f: DetectedField[]) => void) => SceneObject; + getScene: (changeFields: (f: string[]) => void) => SceneObject; } type MakeOptional = Pick, K> & Omit; @@ -58,7 +59,7 @@ type MakeOptional = Pick, K> & Omit; export interface ServiceSceneState extends SceneObjectState { body: SceneFlexLayout; - fields?: DetectedField[]; + fields?: string[]; labels?: string[]; patterns?: LokiPattern[]; @@ -164,10 +165,16 @@ export class ServiceScene extends SceneObjectBase { }); } - private async updateFields() { - const timeRange = sceneGraph.getTimeRange(this).state.value; - const labels = sceneGraph.lookupVariable(VAR_LABELS, this); + private getLogsFormatVariable() { + const variable = sceneGraph.lookupVariable(VAR_LOGS_FORMAT, this); + if (!(variable instanceof CustomVariable)) { + throw new Error('Logs format variable not found'); + } + return variable; + } + private updateFields() { + const variable = this.getLogsFormatVariable(); const disabledFields = [ '__time', 'timestamp', @@ -184,33 +191,33 @@ export class ServiceScene extends SceneObjectBase { 'referer', 'user_identifier', ]; - - if (!(labels instanceof AdHocFiltersVariable)) { - return; - } - const expression = `${labels?.state?.filterExpression}`; - - const ds = await getLokiDatasource(this); - if (!ds) { - return; - } - try { - const response: { fieldLimit: number; fields: DetectedField[] } = await ds.getResource('detected_fields', { - query: expression, - from: timeRange.from.utc().toISOString(), - to: timeRange.to.utc().toISOString(), - }); - + const newState = sceneGraph.getData(this).state; + if (newState.data?.state === LoadingState.Done) { + const frame = newState.data?.series[0]; + if (frame) { + const res = extractParserAndFieldsFromDataFrame(frame); + const fields = res.fields.filter((f) => !disabledFields.includes(f)).sort((a, b) => a.localeCompare(b)); + if (JSON.stringify(fields) !== JSON.stringify(this.state.fields)) { + this.setState({ + fields: fields, + loading: false, + }); + } + const newType = res.type ? ` | ${res.type}` : ''; + if (variable.getValue() !== newType) { + variable.changeValueTo(newType); + } + } else { + this.setState({ + fields: [], + loading: false, + }); + } + } else if (newState.data?.state === LoadingState.Error) { this.setState({ + fields: [], loading: false, - fields: response.fields - .filter((field) => !disabledFields.includes(field.label) && field.cardinality > 1) - .sort((a, b) => a.label.localeCompare(b.label)) - .map((field) => new DetectedField(field.type, field.label, field.parsers, field.cardinality)), }); - } catch (error) { - console.error('Could not fetch detected_fields', error); - this.setState({ loading: false }); } } @@ -348,7 +355,7 @@ export class LogsActionBar extends SceneObjectBase { const getCounter = (tab: BreakdownViewDefinition, state: ServiceSceneState) => { switch (tab.value) { case 'fields': - return state.fieldsCount ?? (state.fields?.filter((l) => l.label !== ALL_VARIABLE_VALUE) ?? []).length; + return state.fieldsCount ?? (state.fields?.filter((l) => l !== ALL_VARIABLE_VALUE) ?? []).length; case 'patterns': return state.patterns?.length; case 'labels': diff --git a/src/models/DetectedField.ts b/src/models/DetectedField.ts deleted file mode 100644 index 7272378c4..000000000 --- a/src/models/DetectedField.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ALL_VARIABLE_VALUE } from 'services/variables'; - -export class DetectedField { - static All = new DetectedField(ALL_VARIABLE_VALUE, 'All', [], 0); - - public type: string; - public label: string; - public parsers: string[]; - public cardinality: number; - - constructor(type: string, label: string, parsers: string[], cardinality: number) { - this.type = type; - this.label = label; - this.parsers = parsers; - this.cardinality = cardinality; - } -} diff --git a/src/services/variables.ts b/src/services/variables.ts index 1757b4090..40e7249c5 100644 --- a/src/services/variables.ts +++ b/src/services/variables.ts @@ -17,4 +17,3 @@ export const EXPLORATION_DS = { uid: VAR_DATASOURCE_EXPR }; export const ALL_VARIABLE_VALUE = '$__all'; export const LEVEL_VARIABLE_VALUE = 'detected_level'; export const PATTERNS_TEXT_FILTER = 'patternsFilter'; -export const LOG_EXPR_WITHOUT_STREAM_SELECTOR = `${VAR_PATTERNS_EXPR} ${VAR_LOGS_FORMAT_EXPR} ${VAR_FIELDS_EXPR} ${VAR_LINE_FILTER_EXPR}`;