diff --git a/packages/jaeger-ui/src/model/transform-trace-data.test.js b/packages/jaeger-ui/src/model/transform-trace-data.test.js new file mode 100644 index 0000000000..99c2bbad25 --- /dev/null +++ b/packages/jaeger-ui/src/model/transform-trace-data.test.js @@ -0,0 +1,55 @@ +// Copyright (c) 2019 The Jaeger Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { orderTags, deduplicateTags } from './transform-trace-data'; + +describe('orderTags()', () => { + it('correctly orders tags', () => { + const orderedTags = orderTags( + [ + { key: 'b.ip', value: '8.8.4.4' }, + { key: 'http.Status_code', value: '200' }, + { key: 'z.ip', value: '8.8.8.16' }, + { key: 'a.ip', value: '8.8.8.8' }, + { key: 'http.message', value: 'ok' }, + ], + ['z.', 'a.', 'HTTP.'] + ); + expect(orderedTags).toEqual([ + { key: 'z.ip', value: '8.8.8.16' }, + { key: 'a.ip', value: '8.8.8.8' }, + { key: 'http.message', value: 'ok' }, + { key: 'http.Status_code', value: '200' }, + { key: 'b.ip', value: '8.8.4.4' }, + ]); + }); +}); + +describe('deduplicateTags()', () => { + it('deduplicates tags', () => { + const tagsInfo = deduplicateTags([ + { key: 'b.ip', value: '8.8.4.4' }, + { key: 'b.ip', value: '8.8.8.8' }, + { key: 'b.ip', value: '8.8.4.4' }, + { key: 'a.ip', value: '8.8.8.8' }, + ]); + + expect(tagsInfo.tags).toEqual([ + { key: 'b.ip', value: '8.8.4.4' }, + { key: 'b.ip', value: '8.8.8.8' }, + { key: 'a.ip', value: '8.8.8.8' }, + ]); + expect(tagsInfo.warnings).toEqual(['Duplicate tag "b.ip:8.8.4.4"']); + }); +}); diff --git a/packages/jaeger-ui/src/model/transform-trace-data.tsx b/packages/jaeger-ui/src/model/transform-trace-data.tsx index 46eeb83c3d..6fc358a7cf 100644 --- a/packages/jaeger-ui/src/model/transform-trace-data.tsx +++ b/packages/jaeger-ui/src/model/transform-trace-data.tsx @@ -15,12 +15,14 @@ import _isEqual from 'lodash/isEqual'; import { getTraceSpanIdsAsTree } from '../selectors/trace'; +import { getConfigValue } from '../utils/config/get-config'; import { KeyValuePair, Span, SpanData, Trace, TraceData } from '../types/trace'; import TreeNode from '../utils/TreeNode'; -function deduplicateTags(spanTags: Array) { +// exported for tests +export function deduplicateTags(spanTags: KeyValuePair[]) { const warningsHash: Map = new Map(); - const tags: Array = spanTags.reduce>((uniqueTags, tag) => { + const tags: KeyValuePair[] = spanTags.reduce((uniqueTags, tag) => { if (!uniqueTags.some(t => t.key === tag.key && t.value === tag.value)) { uniqueTags.push(tag); } else { @@ -32,6 +34,37 @@ function deduplicateTags(spanTags: Array) { return { tags, warnings }; } +// exported for tests +export function orderTags(spanTags: KeyValuePair[], topPrefixes?: string[]) { + const orderedTags: KeyValuePair[] = spanTags.slice(); + const tp = (topPrefixes || []).map((p: string) => p.toLowerCase()); + + orderedTags.sort((a, b) => { + const aKey = a.key.toLowerCase(); + const bKey = b.key.toLowerCase(); + + for (let i = 0; i < tp.length; i++) { + const p = tp[i]; + if (aKey.startsWith(p) && !bKey.startsWith(p)) { + return -1; + } + if (!aKey.startsWith(p) && bKey.startsWith(p)) { + return 1; + } + } + + if (aKey > bKey) { + return 1; + } + if (aKey < bKey) { + return -1; + } + return 0; + }); + + return orderedTags; +} + /** * NOTE: Mutates `data` - Transform the HTTP response data into the form the app * generally requires. @@ -109,7 +142,7 @@ export default function transformTraceData(data: TraceData & { spans: SpanData[] span.tags = span.tags || []; span.references = span.references || []; const tagsInfo = deduplicateTags(span.tags); - span.tags = tagsInfo.tags; + span.tags = orderTags(tagsInfo.tags, getConfigValue('topTagPrefixes')); span.warnings = span.warnings.concat(tagsInfo.warnings); span.references.forEach(ref => { const refSpan = spanMap.get(ref.spanID) as Span; diff --git a/packages/jaeger-ui/src/types/config.tsx b/packages/jaeger-ui/src/types/config.tsx index 9527b7c068..683c82a134 100644 --- a/packages/jaeger-ui/src/types/config.tsx +++ b/packages/jaeger-ui/src/types/config.tsx @@ -36,6 +36,7 @@ export type Config = { menu: (ConfigMenuGroup | ConfigMenuItem)[]; search?: { maxLookback: { label: string; value: string } }; scripts?: TScript[]; + topTagPrefixes?: string[]; tracking?: { gaID: string | TNil; trackErrors: boolean | TNil;