mirror of https://github.com/grafana/grafana
Jaeger: Decouple Jaeger plugin (#81377)
parent
83597e6c37
commit
d1f791cf1f
@ -0,0 +1 @@ |
||||
# Changelog |
@ -1,7 +1,3 @@ |
||||
# Jaeger Data Source - Native Plugin |
||||
# Grafana Jaeger Data Source - Native Plugin |
||||
|
||||
Grafana ships with **built in** support for Jaeger, an open source, end-to-end distributed tracing system. |
||||
|
||||
Read more about it here: |
||||
|
||||
[https://docs.grafana.org/datasources/jaeger/](https://docs.grafana.org/datasources/jaeger/) |
||||
[https://docs.grafana.org/datasources/jaeger/](Grafana plugin for the Jaeger data source). |
||||
|
@ -0,0 +1,3 @@ |
||||
This directory contains dependencies that we duplicated from Grafana core while working on the decoupling of Jaeger from such core. |
||||
The long-term goal is to move these files away from here by replacing them with packages. |
||||
As such, they are only temporary and meant to be used internally to this package, please avoid using them for example as dependencies (imports) in other data source plugins. |
@ -0,0 +1,57 @@ |
||||
// Copyright (c) 2020 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 { memoize } from 'lodash'; |
||||
|
||||
import { TraceSpan } from '../types'; |
||||
|
||||
function _getTraceNameImpl(spans: TraceSpan[]) { |
||||
// Use a span with no references to another span in given array
|
||||
// prefering the span with the fewest references
|
||||
// using start time as a tie breaker
|
||||
let candidateSpan: TraceSpan | undefined; |
||||
const allIDs: Set<string> = new Set(spans.map(({ spanID }) => spanID)); |
||||
|
||||
for (let i = 0; i < spans.length; i++) { |
||||
const hasInternalRef = |
||||
spans[i].references && |
||||
spans[i].references.some(({ traceID, spanID }) => traceID === spans[i].traceID && allIDs.has(spanID)); |
||||
if (hasInternalRef) { |
||||
continue; |
||||
} |
||||
|
||||
if (!candidateSpan) { |
||||
candidateSpan = spans[i]; |
||||
continue; |
||||
} |
||||
|
||||
const thisRefLength = (spans[i].references && spans[i].references.length) || 0; |
||||
const candidateRefLength = (candidateSpan.references && candidateSpan.references.length) || 0; |
||||
|
||||
if ( |
||||
thisRefLength < candidateRefLength || |
||||
(thisRefLength === candidateRefLength && spans[i].startTime < candidateSpan.startTime) |
||||
) { |
||||
candidateSpan = spans[i]; |
||||
} |
||||
} |
||||
return candidateSpan ? `${candidateSpan.process.serviceName}: ${candidateSpan.operationName}` : ''; |
||||
} |
||||
|
||||
export const getTraceName = memoize(_getTraceNameImpl, (spans: TraceSpan[]) => { |
||||
if (!spans.length) { |
||||
return 0; |
||||
} |
||||
return spans[0].traceID; |
||||
}); |
@ -0,0 +1,199 @@ |
||||
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||
//
|
||||
// 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 { isEqual as _isEqual } from 'lodash'; |
||||
|
||||
import { getTraceSpanIdsAsTree } from '../selectors/trace'; |
||||
import { TraceKeyValuePair, TraceSpan, Trace, TraceResponse, TraceProcess } from '../types'; |
||||
import TreeNode from '../utils/TreeNode'; |
||||
import { getConfigValue } from '../utils/config/get-config'; |
||||
|
||||
import { getTraceName } from './trace-viewer'; |
||||
|
||||
function deduplicateTags(tags: TraceKeyValuePair[]) { |
||||
const warningsHash: Map<string, string> = new Map<string, string>(); |
||||
const dedupedTags: TraceKeyValuePair[] = tags.reduce<TraceKeyValuePair[]>((uniqueTags, tag) => { |
||||
if (!uniqueTags.some((t) => t.key === tag.key && t.value === tag.value)) { |
||||
uniqueTags.push(tag); |
||||
} else { |
||||
warningsHash.set(`${tag.key}:${tag.value}`, `Duplicate tag "${tag.key}:${tag.value}"`); |
||||
} |
||||
return uniqueTags; |
||||
}, []); |
||||
const warnings = Array.from(warningsHash.values()); |
||||
return { dedupedTags, warnings }; |
||||
} |
||||
|
||||
function orderTags(tags: TraceKeyValuePair[], topPrefixes?: string[]) { |
||||
const orderedTags: TraceKeyValuePair[] = tags?.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. |
||||
*/ |
||||
export default function transformTraceData(data: TraceResponse | undefined): Trace | null { |
||||
if (!data?.traceID) { |
||||
return null; |
||||
} |
||||
const traceID = data.traceID.toLowerCase(); |
||||
|
||||
let traceEndTime = 0; |
||||
let traceStartTime = Number.MAX_SAFE_INTEGER; |
||||
const spanIdCounts = new Map(); |
||||
const spanMap = new Map<string, TraceSpan>(); |
||||
// filter out spans with empty start times
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
data.spans = data.spans.filter((span) => Boolean(span.startTime)); |
||||
|
||||
// Sort process tags
|
||||
data.processes = Object.entries(data.processes).reduce<Record<string, TraceProcess>>((processes, [id, process]) => { |
||||
processes[id] = { |
||||
...process, |
||||
tags: orderTags(process.tags), |
||||
}; |
||||
return processes; |
||||
}, {}); |
||||
|
||||
const max = data.spans.length; |
||||
for (let i = 0; i < max; i++) { |
||||
const span: TraceSpan = data.spans[i] as TraceSpan; |
||||
const { startTime, duration, processID } = span; |
||||
|
||||
let spanID = span.spanID; |
||||
// check for start / end time for the trace
|
||||
if (startTime < traceStartTime) { |
||||
traceStartTime = startTime; |
||||
} |
||||
if (startTime + duration > traceEndTime) { |
||||
traceEndTime = startTime + duration; |
||||
} |
||||
// make sure span IDs are unique
|
||||
const idCount = spanIdCounts.get(spanID); |
||||
if (idCount != null) { |
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`Dupe spanID, ${idCount + 1} x ${spanID}`, span, spanMap.get(spanID)); |
||||
if (_isEqual(span, spanMap.get(spanID))) { |
||||
// eslint-disable-next-line no-console
|
||||
console.warn('\t two spans with same ID have `isEqual(...) === true`'); |
||||
} |
||||
spanIdCounts.set(spanID, idCount + 1); |
||||
spanID = `${spanID}_${idCount}`; |
||||
span.spanID = spanID; |
||||
} else { |
||||
spanIdCounts.set(spanID, 1); |
||||
} |
||||
span.process = data.processes[processID]; |
||||
spanMap.set(spanID, span); |
||||
} |
||||
// tree is necessary to sort the spans, so children follow parents, and
|
||||
// siblings are sorted by start time
|
||||
const tree = getTraceSpanIdsAsTree(data, spanMap); |
||||
const spans: TraceSpan[] = []; |
||||
const svcCounts: Record<string, number> = {}; |
||||
|
||||
tree.walk((spanID: string, node: TreeNode<string>, depth = 0) => { |
||||
if (spanID === '__root__') { |
||||
return; |
||||
} |
||||
if (typeof spanID !== 'string') { |
||||
return; |
||||
} |
||||
const span = spanMap.get(spanID); |
||||
if (!span) { |
||||
return; |
||||
} |
||||
const { serviceName } = span.process; |
||||
svcCounts[serviceName] = (svcCounts[serviceName] || 0) + 1; |
||||
span.relativeStartTime = span.startTime - traceStartTime; |
||||
span.depth = depth - 1; |
||||
span.hasChildren = node.children.length > 0; |
||||
span.childSpanCount = node.children.length; |
||||
span.warnings = span.warnings || []; |
||||
span.tags = span.tags || []; |
||||
span.references = span.references || []; |
||||
|
||||
span.childSpanIds = node.children |
||||
.slice() |
||||
.sort((a, b) => { |
||||
const spanA = spanMap.get(a.value)!; |
||||
const spanB = spanMap.get(b.value)!; |
||||
return spanB.startTime + spanB.duration - (spanA.startTime + spanA.duration); |
||||
}) |
||||
.map((each) => each.value); |
||||
|
||||
const tagsInfo = deduplicateTags(span.tags); |
||||
span.tags = orderTags(tagsInfo.dedupedTags, getConfigValue('topTagPrefixes')); |
||||
span.warnings = span.warnings.concat(tagsInfo.warnings); |
||||
span.references.forEach((ref, index) => { |
||||
const refSpan = spanMap.get(ref.spanID); |
||||
if (refSpan) { |
||||
// eslint-disable-next-line no-param-reassign
|
||||
ref.span = refSpan; |
||||
if (index > 0) { |
||||
// Don't take into account the parent, just other references.
|
||||
refSpan.subsidiarilyReferencedBy = refSpan.subsidiarilyReferencedBy || []; |
||||
refSpan.subsidiarilyReferencedBy.push({ |
||||
spanID, |
||||
traceID, |
||||
span, |
||||
refType: ref.refType, |
||||
}); |
||||
} |
||||
} |
||||
}); |
||||
spans.push(span); |
||||
}); |
||||
const traceName = getTraceName(spans); |
||||
const services = Object.keys(svcCounts).map((name) => ({ name, numberOfSpans: svcCounts[name] })); |
||||
return { |
||||
services, |
||||
spans, |
||||
traceID, |
||||
traceName, |
||||
// can't use spread operator for intersection types
|
||||
// repl: https://goo.gl/4Z23MJ
|
||||
// issue: https://github.com/facebook/flow/issues/1511
|
||||
processes: data.processes, |
||||
duration: traceEndTime - traceStartTime, |
||||
startTime: traceStartTime, |
||||
endTime: traceEndTime, |
||||
}; |
||||
} |
@ -0,0 +1,65 @@ |
||||
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||
//
|
||||
// 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 { TraceResponse, TraceSpanData } from '../types/trace'; |
||||
import TreeNode from '../utils/TreeNode'; |
||||
|
||||
const TREE_ROOT_ID = '__root__'; |
||||
|
||||
/** |
||||
* Build a tree of { value: spanID, children } items derived from the |
||||
* `span.references` information. The tree represents the grouping of parent / |
||||
* child relationships. The root-most node is nominal in that |
||||
* `.value === TREE_ROOT_ID`. This is done because a root span (the main trace |
||||
* span) is not always included with the trace data. Thus, there can be |
||||
* multiple top-level spans, and the root node acts as their common parent. |
||||
* |
||||
* The children are sorted by `span.startTime` after the tree is built. |
||||
* |
||||
* @param {Trace} trace The trace to build the tree of spanIDs. |
||||
* @return {TreeNode} A tree of spanIDs derived from the relationships |
||||
* between spans in the trace. |
||||
*/ |
||||
export function getTraceSpanIdsAsTree(trace: TraceResponse, spanMap: Map<string, TraceSpanData> | null = null) { |
||||
const nodesById = new Map(trace.spans.map((span: TraceSpanData) => [span.spanID, new TreeNode(span.spanID)])); |
||||
const spansById = spanMap ?? new Map(trace.spans.map((span: TraceSpanData) => [span.spanID, span])); |
||||
const root = new TreeNode(TREE_ROOT_ID); |
||||
trace.spans.forEach((span: TraceSpanData) => { |
||||
const node = nodesById.get(span.spanID)!; |
||||
if (Array.isArray(span.references) && span.references.length) { |
||||
const { refType, spanID: parentID } = span.references[0]; |
||||
if (refType === 'CHILD_OF' || refType === 'FOLLOWS_FROM') { |
||||
const parent = nodesById.get(parentID) || root; |
||||
parent.children?.push(node); |
||||
} else { |
||||
throw new Error(`Unrecognized ref type: ${refType}`); |
||||
} |
||||
} else { |
||||
root.children.push(node); |
||||
} |
||||
}); |
||||
const comparator = (nodeA: TreeNode<string>, nodeB: TreeNode<string>) => { |
||||
const a: TraceSpanData | undefined = nodeA?.value ? spansById.get(nodeA.value.toString()) : undefined; |
||||
const b: TraceSpanData | undefined = nodeB?.value ? spansById.get(nodeB.value.toString()) : undefined; |
||||
return +(a?.startTime! > b?.startTime!) || +(a?.startTime === b?.startTime) - 1; |
||||
}; |
||||
trace.spans.forEach((span: TraceSpanData) => { |
||||
const node = nodesById.get(span.spanID); |
||||
if (node!.children.length > 1) { |
||||
node?.children.sort(comparator); |
||||
} |
||||
}); |
||||
root.children.sort(comparator); |
||||
return root; |
||||
} |
@ -0,0 +1,23 @@ |
||||
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
export { |
||||
TraceSpan, |
||||
TraceResponse, |
||||
Trace, |
||||
TraceProcess, |
||||
TraceKeyValuePair, |
||||
TraceLink, |
||||
CriticalPathSection, |
||||
} from './trace'; |
@ -0,0 +1,112 @@ |
||||
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
/** |
||||
* All timestamps are in microseconds |
||||
*/ |
||||
|
||||
// TODO: Everett Tech Debt: Fix KeyValuePair types
|
||||
export type TraceKeyValuePair = { |
||||
key: string; |
||||
type?: string; |
||||
value: any; |
||||
}; |
||||
|
||||
export type TraceLink = { |
||||
url: string; |
||||
text: string; |
||||
}; |
||||
|
||||
export type TraceLog = { |
||||
timestamp: number; |
||||
fields: TraceKeyValuePair[]; |
||||
}; |
||||
|
||||
export type TraceProcess = { |
||||
serviceName: string; |
||||
tags: TraceKeyValuePair[]; |
||||
}; |
||||
|
||||
export type TraceSpanReference = { |
||||
refType: 'CHILD_OF' | 'FOLLOWS_FROM'; |
||||
// eslint-disable-next-line no-use-before-define
|
||||
span?: TraceSpan | null | undefined; |
||||
spanID: string; |
||||
traceID: string; |
||||
tags?: TraceKeyValuePair[]; |
||||
}; |
||||
|
||||
export type TraceSpanData = { |
||||
spanID: string; |
||||
traceID: string; |
||||
processID: string; |
||||
operationName: string; |
||||
// Times are in microseconds
|
||||
startTime: number; |
||||
duration: number; |
||||
logs: TraceLog[]; |
||||
tags?: TraceKeyValuePair[]; |
||||
kind?: string; |
||||
statusCode?: number; |
||||
statusMessage?: string; |
||||
instrumentationLibraryName?: string; |
||||
instrumentationLibraryVersion?: string; |
||||
traceState?: string; |
||||
references?: TraceSpanReference[]; |
||||
warnings?: string[] | null; |
||||
stackTraces?: string[]; |
||||
flags: number; |
||||
errorIconColor?: string; |
||||
dataFrameRowIndex?: number; |
||||
childSpanIds?: string[]; |
||||
}; |
||||
|
||||
export type TraceSpan = TraceSpanData & { |
||||
depth: number; |
||||
hasChildren: boolean; |
||||
childSpanCount: number; |
||||
process: TraceProcess; |
||||
relativeStartTime: number; |
||||
tags: NonNullable<TraceSpanData['tags']>; |
||||
references: NonNullable<TraceSpanData['references']>; |
||||
warnings: NonNullable<TraceSpanData['warnings']>; |
||||
childSpanIds: NonNullable<TraceSpanData['childSpanIds']>; |
||||
subsidiarilyReferencedBy: TraceSpanReference[]; |
||||
}; |
||||
|
||||
export type TraceData = { |
||||
processes: Record<string, TraceProcess>; |
||||
traceID: string; |
||||
warnings?: string[] | null; |
||||
}; |
||||
|
||||
export type TraceResponse = TraceData & { |
||||
spans: TraceSpanData[]; |
||||
}; |
||||
|
||||
export type Trace = TraceData & { |
||||
duration: number; |
||||
endTime: number; |
||||
spans: TraceSpan[]; |
||||
startTime: number; |
||||
traceName: string; |
||||
services: Array<{ name: string; numberOfSpans: number }>; |
||||
}; |
||||
|
||||
// It is a section of span that lies on critical path
|
||||
export type CriticalPathSection = { |
||||
spanId: string; |
||||
section_start: number; |
||||
section_end: number; |
||||
}; |
@ -0,0 +1,139 @@ |
||||
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||
//
|
||||
// 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
|
||||
|
||||
export default class TreeNode<TValue> { |
||||
value: TValue; |
||||
children: Array<TreeNode<TValue>>; |
||||
|
||||
static iterFunction<TValue>( |
||||
fn: ((value: TValue, node: TreeNode<TValue>, depth: number) => TreeNode<TValue> | null) | Function, |
||||
depth = 0 |
||||
) { |
||||
return (node: TreeNode<TValue>) => fn(node.value, node, depth); |
||||
} |
||||
|
||||
static searchFunction<TValue>(search: Function | TreeNode<TValue>) { |
||||
if (typeof search === 'function') { |
||||
return search; |
||||
} |
||||
|
||||
return (value: TValue, node: TreeNode<TValue>) => (search instanceof TreeNode ? node === search : value === search); |
||||
} |
||||
|
||||
constructor(value: TValue, children: Array<TreeNode<TValue>> = []) { |
||||
this.value = value; |
||||
this.children = children; |
||||
} |
||||
|
||||
get depth(): number { |
||||
return this.children.reduce((depth, child) => Math.max(child.depth + 1, depth), 1); |
||||
} |
||||
|
||||
get size() { |
||||
let i = 0; |
||||
this.walk(() => i++); |
||||
return i; |
||||
} |
||||
|
||||
addChild(child: TreeNode<TValue> | TValue) { |
||||
this.children.push(child instanceof TreeNode ? child : new TreeNode(child)); |
||||
return this; |
||||
} |
||||
|
||||
find(search: Function | TreeNode<TValue>): TreeNode<TValue> | null { |
||||
const searchFn = TreeNode.iterFunction(TreeNode.searchFunction(search)); |
||||
if (searchFn(this)) { |
||||
return this; |
||||
} |
||||
for (let i = 0; i < this.children.length; i++) { |
||||
const result = this.children[i].find(search); |
||||
if (result) { |
||||
return result; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
getPath(search: Function | TreeNode<TValue>) { |
||||
const searchFn = TreeNode.iterFunction(TreeNode.searchFunction(search)); |
||||
|
||||
const findPath = ( |
||||
currentNode: TreeNode<TValue>, |
||||
currentPath: Array<TreeNode<TValue>> |
||||
): Array<TreeNode<TValue>> | null => { |
||||
// skip if we already found the result
|
||||
const attempt = currentPath.concat([currentNode]); |
||||
// base case: return the array when there is a match
|
||||
if (searchFn(currentNode)) { |
||||
return attempt; |
||||
} |
||||
for (let i = 0; i < currentNode.children.length; i++) { |
||||
const child = currentNode.children[i]; |
||||
const match = findPath(child, attempt); |
||||
if (match) { |
||||
return match; |
||||
} |
||||
} |
||||
return null; |
||||
}; |
||||
|
||||
return findPath(this, []); |
||||
} |
||||
|
||||
walk(fn: (spanID: TValue, node: TreeNode<TValue>, depth: number) => void, startDepth = 0) { |
||||
type StackEntry = { |
||||
node: TreeNode<TValue>; |
||||
depth: number; |
||||
}; |
||||
const nodeStack: StackEntry[] = []; |
||||
let actualDepth = startDepth; |
||||
nodeStack.push({ node: this, depth: actualDepth }); |
||||
while (nodeStack.length) { |
||||
const entry: StackEntry = nodeStack[nodeStack.length - 1]; |
||||
nodeStack.pop(); |
||||
const { node, depth } = entry; |
||||
fn(node.value, node, depth); |
||||
actualDepth = depth + 1; |
||||
let i = node.children.length - 1; |
||||
while (i >= 0) { |
||||
nodeStack.push({ node: node.children[i], depth: actualDepth }); |
||||
i--; |
||||
} |
||||
} |
||||
} |
||||
|
||||
paths(fn: (pathIds: TValue[]) => void) { |
||||
type StackEntry = { |
||||
node: TreeNode<TValue>; |
||||
childIndex: number; |
||||
}; |
||||
const stack: StackEntry[] = []; |
||||
stack.push({ node: this, childIndex: 0 }); |
||||
const paths: TValue[] = []; |
||||
while (stack.length) { |
||||
const { node, childIndex } = stack[stack.length - 1]; |
||||
if (node.children.length >= childIndex + 1) { |
||||
stack[stack.length - 1].childIndex++; |
||||
stack.push({ node: node.children[childIndex], childIndex: 0 }); |
||||
} else { |
||||
if (node.children.length === 0) { |
||||
const path = stack.map((item) => item.node.value); |
||||
fn(path); |
||||
} |
||||
stack.pop(); |
||||
} |
||||
} |
||||
return paths; |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
const FALLBACK_DAG_MAX_NUM_SERVICES = 100; |
||||
|
||||
export default Object.defineProperty( |
||||
{ |
||||
archiveEnabled: false, |
||||
dependencies: { |
||||
dagMaxNumServices: FALLBACK_DAG_MAX_NUM_SERVICES, |
||||
menuEnabled: true, |
||||
}, |
||||
linkPatterns: [], |
||||
search: { |
||||
maxLookback: { |
||||
label: '2 Days', |
||||
value: '2d', |
||||
}, |
||||
maxLimit: 1500, |
||||
}, |
||||
tracking: { |
||||
gaID: null, |
||||
trackErrors: true, |
||||
}, |
||||
}, |
||||
// fields that should be individually merged vs wholesale replaced
|
||||
'__mergeFields', |
||||
{ value: ['dependencies', 'search', 'tracking'] } |
||||
); |
@ -0,0 +1,29 @@ |
||||
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||
//
|
||||
// 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 { get as _get } from 'lodash'; |
||||
|
||||
import defaultConfig from './default-config'; |
||||
|
||||
/** |
||||
* Merge the embedded config from the query service (if present) with the |
||||
* default config from `../../constants/default-config`. |
||||
*/ |
||||
export default function getConfig() { |
||||
return defaultConfig; |
||||
} |
||||
|
||||
export function getConfigValue(path: string) { |
||||
return _get(getConfig(), path); |
||||
} |
@ -0,0 +1,15 @@ |
||||
import { FetchResponse } from '@grafana/runtime'; |
||||
|
||||
export function createFetchResponse<T>(data: T): FetchResponse<T> { |
||||
return { |
||||
data, |
||||
status: 200, |
||||
url: 'http://localhost:3000/api/ds/query', |
||||
config: { url: 'http://localhost:3000/api/ds/query' }, |
||||
type: 'basic', |
||||
statusText: 'Ok', |
||||
redirected: false, |
||||
headers: {} as unknown as Headers, |
||||
ok: true, |
||||
}; |
||||
} |
@ -0,0 +1,43 @@ |
||||
{ |
||||
"name": "@grafana-plugins/jaeger", |
||||
"description": "Jaeger plugin for Grafana", |
||||
"private": true, |
||||
"version": "10.4.0-pre", |
||||
"dependencies": { |
||||
"@emotion/css": "11.11.2", |
||||
"@grafana/data": "workspace:*", |
||||
"@grafana/experimental": "1.7.10", |
||||
"@grafana/o11y-ds-frontend": "workspace:*", |
||||
"@grafana/runtime": "workspace:*", |
||||
"@grafana/ui": "workspace:*", |
||||
"lodash": "4.17.21", |
||||
"logfmt": "^1.3.2", |
||||
"react-window": "1.8.10", |
||||
"rxjs": "7.8.1", |
||||
"stream-browserify": "3.0.0", |
||||
"tslib": "2.6.2", |
||||
"uuid": "9.0.1" |
||||
}, |
||||
"devDependencies": { |
||||
"@grafana/plugin-configs": "workspace:*", |
||||
"@testing-library/jest-dom": "6.4.2", |
||||
"@testing-library/react": "14.2.1", |
||||
"@testing-library/user-event": "14.5.2", |
||||
"@types/jest": "29.5.12", |
||||
"@types/lodash": "4.17.0", |
||||
"@types/logfmt": "^1.2.3", |
||||
"@types/react-window": "1.8.8", |
||||
"@types/uuid": "9.0.8", |
||||
"ts-node": "10.9.2", |
||||
"webpack": "5.90.3" |
||||
}, |
||||
"peerDependencies": { |
||||
"@grafana/runtime": "*" |
||||
}, |
||||
"scripts": { |
||||
"build": "webpack -c ./webpack.config.ts --env production", |
||||
"build:commit": "webpack -c ./webpack.config.ts --env production --env commit=$(git rev-parse --short HEAD)", |
||||
"dev": "webpack -w -c ./webpack.config.ts --env development" |
||||
}, |
||||
"packageManager": "yarn@3.6.0" |
||||
} |
@ -0,0 +1,7 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"types": ["jest", "@testing-library/jest-dom"] |
||||
}, |
||||
"extends": "@grafana/plugin-configs/tsconfig.json", |
||||
"include": ["."] |
||||
} |
@ -0,0 +1,14 @@ |
||||
import config from '@grafana/plugin-configs/webpack.config'; |
||||
|
||||
const configWithFallback = async (env: Record<string, unknown>) => { |
||||
const response = await config(env); |
||||
if (response !== undefined && response.resolve !== undefined) { |
||||
response.resolve.fallback = { |
||||
...response.resolve.fallback, |
||||
stream: require.resolve('stream-browserify'), |
||||
}; |
||||
} |
||||
return response; |
||||
}; |
||||
|
||||
export default configWithFallback; |
Loading…
Reference in new issue