Skip to content

Commit 9d8e96c

Browse files
[Discover] [Metrics] Hide internal dimensions from metrics breakdown selector and flyout (elastic#259905)
## Summary Filter out internal/metadata dimensions (_metric_names_hash, unit, labels._*) from the parsed metrics response so they don't appear in the breakdown dimension selector or the view-details flyout. The filtering is extensible via constant collections for exact names and prefix patterns. Resolves [elastic/observability-dev#5412](elastic/observability-dev#5412) Internal dimensions like `_metric_names_hash`, `unit`, and `labels._*` are visible in the breakdown dimension selector and the view-details flyout. These are internal/metadata fields that shouldn't be exposed to users. This PR filters them out in `parseMetricsResponse` before they reach the UI, using: - A `Set` for exact-match names (`_metric_names_hash`, `unit`) - A prefix array for pattern matching (`labels._`) This is a client-side filter; when/if the server-side `METRICS_INFO` command excludes these natively, the filter becomes a harmless no-op. ## Test plan - [ ] Open Discover with metrics data stream - [ ] Verify `_metric_names_hash`, `unit`, and `labels._*` do **not** appear in the breakdown dimension selector - [ ] Click View Details on a metric and verify those dimensions are absent from the flyout - [ ] Verify legitimate `labels.*` dimensions (e.g., `labels.environment`) still appear Co-authored-by: Lucas Francisco López <lucaslopezf@gmail.com>
1 parent d5853f1 commit 9d8e96c

2 files changed

Lines changed: 119 additions & 1 deletion

File tree

src/platform/packages/shared/kbn-unified-chart-section-viewer/src/components/observability/metrics/utils/parse_metrics_response.test.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,103 @@ describe('parseMetricsResponse', () => {
205205
expect(dimensionNames).toContain('container.id');
206206
});
207207

208+
describe('internal dimension filtering', () => {
209+
it('filters out internal dimension _metric_names_hash', () => {
210+
const response: MetricsESQLResponse[] = [
211+
{
212+
metric_name: 'cpu.usage',
213+
data_stream: 'my-index',
214+
unit: ['percent'],
215+
metric_type: 'gauge',
216+
field_type: ES_FIELD_TYPES.DOUBLE,
217+
dimension_fields: ['host.name', '_metric_names_hash'],
218+
},
219+
];
220+
const result = parseMetricsResponse(response);
221+
expect(result.metricItems[0].dimensionFields).toEqual([{ name: 'host.name' }]);
222+
expect(result.allDimensions).toEqual([{ name: 'host.name' }]);
223+
});
224+
225+
it('filters out internal dimension unit', () => {
226+
const response: MetricsESQLResponse[] = [
227+
{
228+
metric_name: 'cpu.usage',
229+
data_stream: 'my-index',
230+
unit: ['percent'],
231+
metric_type: 'gauge',
232+
field_type: ES_FIELD_TYPES.DOUBLE,
233+
dimension_fields: ['host.name', 'unit'],
234+
},
235+
];
236+
const result = parseMetricsResponse(response);
237+
expect(result.metricItems[0].dimensionFields).toEqual([{ name: 'host.name' }]);
238+
expect(result.allDimensions).toEqual([{ name: 'host.name' }]);
239+
});
240+
241+
it('filters out internal dimensions with labels._ prefix', () => {
242+
const response: MetricsESQLResponse[] = [
243+
{
244+
metric_name: 'cpu.usage',
245+
data_stream: 'my-index',
246+
unit: ['percent'],
247+
metric_type: 'gauge',
248+
field_type: ES_FIELD_TYPES.DOUBLE,
249+
dimension_fields: ['host.name', 'labels._foo_', 'labels._bar_baz_'],
250+
},
251+
];
252+
const result = parseMetricsResponse(response);
253+
expect(result.metricItems[0].dimensionFields).toEqual([{ name: 'host.name' }]);
254+
expect(result.allDimensions).toEqual([{ name: 'host.name' }]);
255+
});
256+
257+
it('filters out all internal dimensions while keeping valid ones', () => {
258+
const response: MetricsESQLResponse[] = [
259+
{
260+
metric_name: 'cpu.usage',
261+
data_stream: 'my-index',
262+
unit: ['percent'],
263+
metric_type: 'gauge',
264+
field_type: ES_FIELD_TYPES.DOUBLE,
265+
dimension_fields: [
266+
'host.name',
267+
'_metric_names_hash',
268+
'unit',
269+
'labels._internal_',
270+
'pod.name',
271+
],
272+
},
273+
];
274+
const result = parseMetricsResponse(response);
275+
expect(result.metricItems[0].dimensionFields).toEqual([
276+
{ name: 'host.name' },
277+
{ name: 'pod.name' },
278+
]);
279+
expect(result.allDimensions).toEqual([{ name: 'host.name' }, { name: 'pod.name' }]);
280+
});
281+
282+
it('does not filter non-internal labels dimensions', () => {
283+
const response: MetricsESQLResponse[] = [
284+
{
285+
metric_name: 'cpu.usage',
286+
data_stream: 'my-index',
287+
unit: ['percent'],
288+
metric_type: 'gauge',
289+
field_type: ES_FIELD_TYPES.DOUBLE,
290+
dimension_fields: ['labels.environment', 'labels.team'],
291+
},
292+
];
293+
const result = parseMetricsResponse(response);
294+
expect(result.metricItems[0].dimensionFields).toEqual([
295+
{ name: 'labels.environment' },
296+
{ name: 'labels.team' },
297+
]);
298+
expect(result.allDimensions).toEqual([
299+
{ name: 'labels.environment' },
300+
{ name: 'labels.team' },
301+
]);
302+
});
303+
});
304+
208305
describe('with getFieldType', () => {
209306
const getFieldType = (name: string): string | undefined => {
210307
const types: Record<string, string> = {

src/platform/packages/shared/kbn-unified-chart-section-viewer/src/components/observability/metrics/utils/parse_metrics_response.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,25 @@ import { ALLOWED_METRIC_TYPES } from '../../../../common/constants';
1818

1919
const ALLOWED_METRIC_TYPES_SET = new Set(ALLOWED_METRIC_TYPES);
2020

21+
/**
22+
* Dimension names that are internal metadata and should not be exposed to users.
23+
* See: https://github.com/elastic/observability-dev/issues/5412
24+
*/
25+
const INTERNAL_DIMENSION_EXACT_NAMES = new Set(['_metric_names_hash', 'unit']);
26+
27+
/**
28+
* Dimension name prefixes that indicate internal metadata fields.
29+
* Any dimension whose name starts with one of these prefixes will be hidden.
30+
*/
31+
const INTERNAL_DIMENSION_PREFIXES = ['labels._'];
32+
33+
const isInternalDimension = (name: string): boolean => {
34+
if (INTERNAL_DIMENSION_EXACT_NAMES.has(name)) {
35+
return true;
36+
}
37+
return INTERNAL_DIMENSION_PREFIXES.some((prefix) => name.startsWith(prefix));
38+
};
39+
2140
export const parseMetricsResponse = (
2241
response: MetricsESQLResponse[],
2342
getFieldType?: (name: string) => string | undefined
@@ -44,7 +63,9 @@ export const parseMetricsResponse = (
4463
const dataStreams = toArray(metric.data_stream);
4564
const units = toArray(metric.unit);
4665
const fieldTypes = toArray(metric.field_type);
47-
const dimensions = toArray(metric.dimension_fields);
66+
const dimensions = toArray(metric.dimension_fields).filter(
67+
(name) => !isInternalDimension(name)
68+
);
4869

4970
const dimensionFields = dimensions.map((name) => {
5071
allDimensionsSet.add(name);

0 commit comments

Comments
 (0)