Transformations: Deduplicate names when using extract fields transformation. (#77569)

* dedupe field names

* add test to make sure we don't break normal case
This commit is contained in:
Oscar Kilhed 2023-11-02 16:47:42 +01:00 committed by GitHub
parent 45d59cf31b
commit 0eda368d32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 50 additions and 4 deletions

View File

@ -161,6 +161,7 @@ Experimental features might be changed or removed without prior notice.
| `alertmanagerRemotePrimary` | Enable Grafana to have a remote Alertmanager instance as the primary Alertmanager. |
| `alertmanagerRemoteOnly` | Disable the internal Alertmanager and only use the external one defined. |
| `annotationPermissionUpdate` | Separate annotation permissions from dashboard permissions to allow for more granular control. |
| `extractFieldsNameDeduplication` | Make sure extracted field names are unique in the dataframe |
## Development feature toggles

View File

@ -159,7 +159,7 @@ export function calculateFieldDisplayName(field: Field, frame?: DataFrame, allFr
return displayName;
}
function getUniqueFieldName(field: Field, frame?: DataFrame) {
export function getUniqueFieldName(field: Field, frame?: DataFrame) {
let dupeCount = 0;
let foundSelf = false;

View File

@ -14,5 +14,5 @@ export { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry';
export { sortThresholds, getActiveThreshold } from './thresholds';
export { applyFieldOverrides, validateFieldConfig, applyRawFieldOverrides, useFieldOverrides } from './fieldOverrides';
export { getFieldDisplayValuesProxy } from './getFieldDisplayValuesProxy';
export { getFieldDisplayName, getFrameDisplayName, cacheFieldDisplayNames } from './fieldState';
export { getFieldDisplayName, getFrameDisplayName, cacheFieldDisplayNames, getUniqueFieldName } from './fieldState';
export { getScaleCalculator, getFieldConfigWithMinMax, getMinMaxAndDelta } from './scale';

View File

@ -155,4 +155,5 @@ export interface FeatureToggles {
alertmanagerRemotePrimary?: boolean;
alertmanagerRemoteOnly?: boolean;
annotationPermissionUpdate?: boolean;
extractFieldsNameDeduplication?: boolean;
}

View File

@ -960,5 +960,12 @@ var (
RequiresDevMode: false,
Owner: grafanaAuthnzSquad,
},
{
Name: "extractFieldsNameDeduplication",
Description: "Make sure extracted field names are unique in the dataframe",
Stage: FeatureStageExperimental,
FrontendOnly: true,
Owner: grafanaBiSquad,
},
}
)

View File

@ -136,3 +136,4 @@ alertmanagerRemoteSecondary,experimental,@grafana/alerting-squad,false,false,fal
alertmanagerRemotePrimary,experimental,@grafana/alerting-squad,false,false,false,false
alertmanagerRemoteOnly,experimental,@grafana/alerting-squad,false,false,false,false
annotationPermissionUpdate,experimental,@grafana/grafana-authnz-team,false,false,false,false
extractFieldsNameDeduplication,experimental,@grafana/grafana-bi-squad,false,false,false,true

1 Name Stage Owner requiresDevMode RequiresLicense RequiresRestart FrontendOnly
136 alertmanagerRemotePrimary experimental @grafana/alerting-squad false false false false
137 alertmanagerRemoteOnly experimental @grafana/alerting-squad false false false false
138 annotationPermissionUpdate experimental @grafana/grafana-authnz-team false false false false
139 extractFieldsNameDeduplication experimental @grafana/grafana-bi-squad false false false true

View File

@ -554,4 +554,8 @@ const (
// FlagAnnotationPermissionUpdate
// Separate annotation permissions from dashboard permissions to allow for more granular control.
FlagAnnotationPermissionUpdate = "annotationPermissionUpdate"
// FlagExtractFieldsNameDeduplication
// Make sure extracted field names are unique in the dataframe
FlagExtractFieldsNameDeduplication = "extractFieldsNameDeduplication"
)

View File

@ -7,8 +7,10 @@ import {
Field,
FieldType,
getFieldTypeFromValue,
getUniqueFieldName,
SynchronousDataTransformerInfo,
} from '@grafana/data';
import { config } from '@grafana/runtime';
import { findField } from 'app/features/dimensions';
import { fieldExtractors } from './fieldExtractors';
@ -30,7 +32,7 @@ export const extractFieldsTransformer: SynchronousDataTransformerInfo<ExtractFie
},
};
function addExtractedFields(frame: DataFrame, options: ExtractFieldsOptions): DataFrame {
export function addExtractedFields(frame: DataFrame, options: ExtractFieldsOptions): DataFrame {
if (!options.source) {
return frame;
}
@ -94,12 +96,16 @@ function addExtractedFields(frame: DataFrame, options: ExtractFieldsOptions): Da
const fields = names.map((name) => {
const buffer = values.get(name);
return {
const field = {
name,
values: buffer,
type: buffer ? getFieldTypeFromValue(buffer.find((v) => v != null)) : FieldType.other,
config: {},
} as Field;
if (config.featureToggles.extractFieldsNameDeduplication) {
field.name = getUniqueFieldName(field, frame);
}
return field;
});
if (options.keepTime) {

View File

@ -1,3 +1,7 @@
import { FieldType, toDataFrame } from '@grafana/data';
import { config } from '@grafana/runtime';
import { addExtractedFields } from './extractFields';
import { fieldExtractors } from './fieldExtractors';
import { FieldExtractorID } from './types';
@ -111,4 +115,26 @@ describe('Extract fields from text', () => {
}
`);
});
it('deduplicates names', async () => {
const frame = toDataFrame({
fields: [{ name: 'foo', type: FieldType.string, values: ['{"foo":"extracedValue1"}'] }],
});
config.featureToggles.extractFieldsNameDeduplication = true;
const newFrame = addExtractedFields(frame, { format: FieldExtractorID.JSON, source: 'foo' });
config.featureToggles.extractFieldsNameDeduplication = false;
expect(newFrame.fields.length).toBe(2);
expect(newFrame.fields[1].name).toBe('foo 1');
});
it('keeps correct names when deduplication is active', async () => {
const frame = toDataFrame({
fields: [{ name: 'foo', type: FieldType.string, values: ['{"bar":"extracedValue1"}'] }],
});
config.featureToggles.extractFieldsNameDeduplication = true;
const newFrame = addExtractedFields(frame, { format: FieldExtractorID.JSON, source: 'foo' });
config.featureToggles.extractFieldsNameDeduplication = false;
expect(newFrame.fields.length).toBe(2);
expect(newFrame.fields[1].name).toBe('bar');
});
});