@perses-dev/tempo-plugin 0.48.0 → 0.49.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/cjs/components/TraceQLEditor.js +57 -16
  2. package/dist/cjs/components/complete.js +27 -8
  3. package/dist/cjs/plugins/tempo-trace-query/TempoTraceQuery.js +1 -0
  4. package/dist/cjs/plugins/tempo-trace-query/TempoTraceQueryEditor.js +48 -9
  5. package/dist/cjs/plugins/tempo-trace-query/get-trace-data.js +12 -12
  6. package/dist/cjs/plugins/tempo-trace-query/query-editor-model.js +44 -3
  7. package/dist/cjs/test/mock-data.js +160 -138
  8. package/dist/components/TraceQLEditor.d.ts.map +1 -1
  9. package/dist/components/TraceQLEditor.js +59 -18
  10. package/dist/components/TraceQLEditor.js.map +1 -1
  11. package/dist/components/complete.d.ts +0 -1
  12. package/dist/components/complete.d.ts.map +1 -1
  13. package/dist/components/complete.js +27 -8
  14. package/dist/components/complete.js.map +1 -1
  15. package/dist/model/api-types.d.ts +4 -0
  16. package/dist/model/api-types.d.ts.map +1 -1
  17. package/dist/model/api-types.js.map +1 -1
  18. package/dist/model/trace-query-model.d.ts +1 -0
  19. package/dist/model/trace-query-model.d.ts.map +1 -1
  20. package/dist/model/trace-query-model.js.map +1 -1
  21. package/dist/plugins/tempo-trace-query/TempoTraceQuery.d.ts +1 -0
  22. package/dist/plugins/tempo-trace-query/TempoTraceQuery.d.ts.map +1 -1
  23. package/dist/plugins/tempo-trace-query/TempoTraceQuery.js +1 -0
  24. package/dist/plugins/tempo-trace-query/TempoTraceQuery.js.map +1 -1
  25. package/dist/plugins/tempo-trace-query/TempoTraceQueryEditor.d.ts.map +1 -1
  26. package/dist/plugins/tempo-trace-query/TempoTraceQueryEditor.js +52 -13
  27. package/dist/plugins/tempo-trace-query/TempoTraceQueryEditor.js.map +1 -1
  28. package/dist/plugins/tempo-trace-query/get-trace-data.d.ts.map +1 -1
  29. package/dist/plugins/tempo-trace-query/get-trace-data.js +12 -12
  30. package/dist/plugins/tempo-trace-query/get-trace-data.js.map +1 -1
  31. package/dist/plugins/tempo-trace-query/query-editor-model.d.ts +9 -0
  32. package/dist/plugins/tempo-trace-query/query-editor-model.d.ts.map +1 -1
  33. package/dist/plugins/tempo-trace-query/query-editor-model.js +35 -0
  34. package/dist/plugins/tempo-trace-query/query-editor-model.js.map +1 -1
  35. package/dist/test/mock-data.d.ts.map +1 -1
  36. package/dist/test/mock-data.js +160 -138
  37. package/dist/test/mock-data.js.map +1 -1
  38. package/package.json +4 -4
  39. package/dist/cjs/plugins/tempo-trace-query/DashboardTempoTraceQueryEditor.js +0 -61
  40. package/dist/plugins/tempo-trace-query/DashboardTempoTraceQueryEditor.d.ts +0 -13
  41. package/dist/plugins/tempo-trace-query/DashboardTempoTraceQueryEditor.d.ts.map +0 -1
  42. package/dist/plugins/tempo-trace-query/DashboardTempoTraceQueryEditor.js +0 -53
  43. package/dist/plugins/tempo-trace-query/DashboardTempoTraceQueryEditor.js.map +0 -1
@@ -75,24 +75,65 @@ function TraceQLEditor({ completeConfig, ...rest }) {
75
75
  }, [
76
76
  completeConfig
77
77
  ]);
78
+ const codemirrorTheme = (0, _react.useMemo)(()=>{
79
+ // https://github.com/mui/material-ui/blob/v5.16.7/packages/mui-material/src/OutlinedInput/OutlinedInput.js#L43
80
+ const borderColor = theme.palette.mode === 'light' ? 'rgba(0, 0, 0, 0.23)' : 'rgba(255, 255, 255, 0.23)';
81
+ return _reactcodemirror.EditorView.theme({
82
+ '&': {
83
+ backgroundColor: 'transparent !important',
84
+ border: `1px solid ${borderColor}`,
85
+ borderRadius: `${theme.shape.borderRadius}px`
86
+ },
87
+ '&.cm-focused.cm-editor': {
88
+ outline: 'none'
89
+ },
90
+ '.cm-content': {
91
+ padding: '8px'
92
+ }
93
+ });
94
+ }, [
95
+ theme
96
+ ]);
78
97
  var _rest_value;
79
- return /*#__PURE__*/ (0, _jsxruntime.jsx)(_reactcodemirror.default, {
80
- ...rest,
81
- style: {
82
- border: `1px solid ${theme.palette.divider}`
83
- },
84
- theme: isDarkMode ? 'dark' : 'light',
85
- basicSetup: {
86
- highlightActiveLine: false,
87
- highlightActiveLineGutter: false,
88
- foldGutter: false,
89
- // The explore view accepts either a TraceQL query or a Trace ID as input. The lezer grammar marks Trace IDs as invalid,
90
- // therefore let's disable syntax highlighting if the input is a Trace ID.
91
- syntaxHighlighting: !(0, _core.isValidTraceId)((_rest_value = rest.value) !== null && _rest_value !== void 0 ? _rest_value : '')
98
+ return /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Stack, {
99
+ position: "relative",
100
+ sx: {
101
+ flexGrow: 1
92
102
  },
93
- extensions: [
94
- _reactcodemirror.EditorView.lineWrapping,
95
- traceQLExtension
103
+ children: [
104
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_material // reproduce the same kind of input label that regular MUI TextFields have
105
+ .InputLabel, {
106
+ shrink: true,
107
+ sx: {
108
+ position: 'absolute',
109
+ top: '-6px',
110
+ left: '10px',
111
+ padding: '0 4px',
112
+ color: theme.palette.text.primary,
113
+ backgroundColor: theme.palette.background.default,
114
+ zIndex: 1
115
+ },
116
+ children: "TraceQL Expression"
117
+ }),
118
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_reactcodemirror.default, {
119
+ ...rest,
120
+ theme: isDarkMode ? 'dark' : 'light',
121
+ basicSetup: {
122
+ lineNumbers: false,
123
+ highlightActiveLine: false,
124
+ highlightActiveLineGutter: false,
125
+ foldGutter: false,
126
+ // The explore view accepts either a TraceQL query or a Trace ID as input. The lezer grammar marks Trace IDs as invalid,
127
+ // therefore let's disable syntax highlighting if the input is a Trace ID.
128
+ syntaxHighlighting: !(0, _core.isValidTraceId)((_rest_value = rest.value) !== null && _rest_value !== void 0 ? _rest_value : '')
129
+ },
130
+ extensions: [
131
+ _reactcodemirror.EditorView.lineWrapping,
132
+ traceQLExtension,
133
+ codemirrorTheme
134
+ ],
135
+ placeholder: 'Example: {span.http.method = "GET"}'
136
+ })
96
137
  ]
97
138
  });
98
139
  }
@@ -28,6 +28,7 @@ _export(exports, {
28
28
  return identifyCompletions;
29
29
  }
30
30
  });
31
+ const _autocomplete = require("@codemirror/autocomplete");
31
32
  const _language = require("@codemirror/language");
32
33
  const _lezertraceql = require("@grafana/lezer-traceql");
33
34
  async function complete({ state, pos }, client) {
@@ -196,8 +197,7 @@ function identifyCompletions(state, pos, tree) {
196
197
  scopes: [
197
198
  {
198
199
  kind: 'TagValue',
199
- tag: attribute,
200
- quotes: true
200
+ tag: attribute
201
201
  }
202
202
  ],
203
203
  from: pos
@@ -207,7 +207,8 @@ function identifyCompletions(state, pos, tree) {
207
207
  case _lezertraceql.String:
208
208
  var _node_parent_parent_parent_firstChild, _node_parent_parent_parent, _node_parent_parent, _node_parent5;
209
209
  // autocomplete { resource.service.name="
210
- if (((_node_parent5 = node.parent) === null || _node_parent5 === void 0 ? void 0 : (_node_parent_parent = _node_parent5.parent) === null || _node_parent_parent === void 0 ? void 0 : (_node_parent_parent_parent = _node_parent_parent.parent) === null || _node_parent_parent_parent === void 0 ? void 0 : (_node_parent_parent_parent_firstChild = _node_parent_parent_parent.firstChild) === null || _node_parent_parent_parent_firstChild === void 0 ? void 0 : _node_parent_parent_parent_firstChild.type.id) === _lezertraceql.FieldExpression) {
210
+ // do not autocomplete if cursor is after closing quotes { resource.service.name=""
211
+ if (((_node_parent5 = node.parent) === null || _node_parent5 === void 0 ? void 0 : (_node_parent_parent = _node_parent5.parent) === null || _node_parent_parent === void 0 ? void 0 : (_node_parent_parent_parent = _node_parent_parent.parent) === null || _node_parent_parent_parent === void 0 ? void 0 : (_node_parent_parent_parent_firstChild = _node_parent_parent_parent.firstChild) === null || _node_parent_parent_parent_firstChild === void 0 ? void 0 : _node_parent_parent_parent_firstChild.type.id) === _lezertraceql.FieldExpression && !/^".*"$/.test(state.sliceDoc(node.from, pos))) {
211
212
  const fieldExpr = node.parent.parent.parent.firstChild;
212
213
  const attribute = state.sliceDoc(fieldExpr.from, fieldExpr.to);
213
214
  return {
@@ -227,6 +228,8 @@ function identifyCompletions(state, pos, tree) {
227
228
  if (((_node_prevSibling = node.prevSibling) === null || _node_prevSibling === void 0 ? void 0 : _node_prevSibling.type.id) === _lezertraceql.FieldOp && ((_node_parent6 = node.parent) === null || _node_parent6 === void 0 ? void 0 : (_node_parent_firstChild4 = _node_parent6.firstChild) === null || _node_parent_firstChild4 === void 0 ? void 0 : _node_parent_firstChild4.type.id) === _lezertraceql.FieldExpression) {
228
229
  const fieldExpr = node.parent.firstChild;
229
230
  const attribute = state.sliceDoc(fieldExpr.from, fieldExpr.to);
231
+ // ignore leading " in { name="HT
232
+ const from = state.sliceDoc(node.from, node.from + 1) === '"' ? node.from + 1 : node.from;
230
233
  return {
231
234
  scopes: [
232
235
  {
@@ -234,7 +237,7 @@ function identifyCompletions(state, pos, tree) {
234
237
  tag: attribute
235
238
  }
236
239
  ],
237
- from: node.from
240
+ from
238
241
  };
239
242
  }
240
243
  // autocomplete { s
@@ -279,7 +282,7 @@ function identifyCompletions(state, pos, tree) {
279
282
  break;
280
283
  case 'TagValue':
281
284
  if (client) {
282
- results.push(completeTagValue(client, completion.tag, completion.quotes));
285
+ results.push(completeTagValue(client, completion.tag));
283
286
  }
284
287
  break;
285
288
  }
@@ -297,7 +300,23 @@ async function completeTagName(client, scope) {
297
300
  label: tag
298
301
  }));
299
302
  }
300
- async function completeTagValue(client, tag, quotes) {
303
+ /**
304
+ * Add quotes to the completion text in case quotes are not present already.
305
+ * This handles the following cases:
306
+ * { name=HTTP
307
+ * { name="x
308
+ * { name="x" where cursor is after the 'x'
309
+ */ function applyQuotedCompletion(view, completion, from, to) {
310
+ let insertText = completion.label;
311
+ if (view.state.sliceDoc(from - 1, from) !== '"') {
312
+ insertText = '"' + insertText;
313
+ }
314
+ if (view.state.sliceDoc(to, to + 1) !== '"') {
315
+ insertText = insertText + '"';
316
+ }
317
+ view.dispatch((0, _autocomplete.insertCompletionText)(view.state, insertText, from, to));
318
+ }
319
+ async function completeTagValue(client, tag) {
301
320
  const response = await client.searchTagValues({
302
321
  tag
303
322
  });
@@ -306,8 +325,8 @@ async function completeTagValue(client, tag, quotes) {
306
325
  switch(type){
307
326
  case 'string':
308
327
  completions.push({
309
- displayLabel: value,
310
- label: quotes ? `"${value}"` : value
328
+ label: value,
329
+ apply: applyQuotedCompletion
311
330
  });
312
331
  break;
313
332
  case 'keyword':
@@ -27,6 +27,7 @@ const TempoTraceQuery = {
27
27
  OptionsEditorComponent: _TempoTraceQueryEditor.TempoTraceQueryEditor,
28
28
  createInitialOptions: ()=>({
29
29
  query: '',
30
+ limit: 20,
30
31
  datasource: undefined
31
32
  })
32
33
  };
@@ -23,16 +23,17 @@ Object.defineProperty(exports, "TempoTraceQueryEditor", {
23
23
  const _jsxruntime = require("react/jsx-runtime");
24
24
  const _pluginsystem = require("@perses-dev/plugin-system");
25
25
  const _immer = require("immer");
26
+ const _material = require("@mui/material");
26
27
  const _temposelectors = require("../../model/tempo-selectors");
28
+ const _components = require("../../components");
27
29
  const _queryeditormodel = require("./query-editor-model");
28
- const _DashboardTempoTraceQueryEditor = require("./DashboardTempoTraceQueryEditor");
29
30
  function TempoTraceQueryEditor(props) {
30
31
  const { onChange, value } = props;
31
32
  const { datasource } = value;
32
33
  const selectedDatasource = datasource !== null && datasource !== void 0 ? datasource : _temposelectors.DEFAULT_TEMPO;
33
34
  const { data: client } = (0, _pluginsystem.useDatasourceClient)(selectedDatasource);
34
- const datasourceURL = client === null || client === void 0 ? void 0 : client.options.datasourceUrl;
35
35
  const { query, handleQueryChange, handleQueryBlur } = (0, _queryeditormodel.useQueryState)(props);
36
+ const { limit, handleLimitChange, handleLimitBlur, limitHasError } = (0, _queryeditormodel.useLimitState)(props);
36
37
  const handleDatasourceChange = (next)=>{
37
38
  if ((0, _temposelectors.isTempoDatasourceSelector)(next)) {
38
39
  onChange((0, _immer.produce)(value, (draft)=>{
@@ -44,12 +45,50 @@ function TempoTraceQueryEditor(props) {
44
45
  }
45
46
  throw new Error('Got unexpected non-Tempo datasource selector');
46
47
  };
47
- return /*#__PURE__*/ (0, _jsxruntime.jsx)(_DashboardTempoTraceQueryEditor.DashboardTempoTraceQueryEditor, {
48
- selectedDatasource: selectedDatasource,
49
- handleDatasourceChange: handleDatasourceChange,
50
- datasourceURL: datasourceURL,
51
- query: query,
52
- handleQueryChange: handleQueryChange,
53
- handleQueryBlur: handleQueryBlur
48
+ return /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Stack, {
49
+ spacing: 2,
50
+ children: [
51
+ /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.FormControl, {
52
+ margin: "dense",
53
+ fullWidth: false,
54
+ children: [
55
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.InputLabel, {
56
+ id: "tempo-datasource-label",
57
+ children: "Tempo Datasource"
58
+ }),
59
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_pluginsystem.DatasourceSelect, {
60
+ datasourcePluginKind: _temposelectors.TEMPO_DATASOURCE_KIND,
61
+ value: selectedDatasource,
62
+ onChange: handleDatasourceChange,
63
+ labelId: "tempo-datasource-label",
64
+ label: "Tempo Datasource"
65
+ })
66
+ ]
67
+ }),
68
+ /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Stack, {
69
+ direction: "row",
70
+ spacing: 2,
71
+ children: [
72
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.TraceQLEditor, {
73
+ completeConfig: {
74
+ client
75
+ },
76
+ value: query,
77
+ onChange: handleQueryChange,
78
+ onBlur: handleQueryBlur
79
+ }),
80
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.TextField, {
81
+ label: "Max Traces",
82
+ value: limit,
83
+ error: limitHasError,
84
+ onChange: (e)=>handleLimitChange(e.target.value),
85
+ onBlur: handleLimitBlur,
86
+ sx: {
87
+ width: '110px'
88
+ }
89
+ })
90
+ ]
91
+ })
92
+ ]
54
93
  });
55
94
  }
@@ -53,19 +53,19 @@ const getTraceData = async (spec, context)=>{
53
53
  var _spec_datasource;
54
54
  const client = await context.datasourceStore.getDatasourceClient((_spec_datasource = spec.datasource) !== null && _spec_datasource !== void 0 ? _spec_datasource : defaultTempoDatasource);
55
55
  const getQuery = ()=>{
56
- // if time range not defined -- only return the query from the spec
57
- if (context.absoluteTimeRange === undefined) {
58
- return {
59
- q: spec.query
60
- };
61
- }
62
- // handle time range selection from UI drop down (e.g. last 5 minutes, last 1 hour )
63
- const { start, end } = getUnixTimeRange(context === null || context === void 0 ? void 0 : context.absoluteTimeRange);
64
- return {
65
- q: spec.query,
66
- start,
67
- end
56
+ const params = {
57
+ q: spec.query
68
58
  };
59
+ // handle time range selection from UI drop down (e.g. last 5 minutes, last 1 hour )
60
+ if (context.absoluteTimeRange) {
61
+ const { start, end } = getUnixTimeRange(context.absoluteTimeRange);
62
+ params.start = start;
63
+ params.end = end;
64
+ }
65
+ if (spec.limit) {
66
+ params.limit = spec.limit;
67
+ }
68
+ return params;
69
69
  };
70
70
  /**
71
71
  * determine type of query:
@@ -14,9 +14,17 @@
14
14
  Object.defineProperty(exports, "__esModule", {
15
15
  value: true
16
16
  });
17
- Object.defineProperty(exports, "useQueryState", {
18
- enumerable: true,
19
- get: function() {
17
+ function _export(target, all) {
18
+ for(var name in all)Object.defineProperty(target, name, {
19
+ enumerable: true,
20
+ get: all[name]
21
+ });
22
+ }
23
+ _export(exports, {
24
+ useLimitState: function() {
25
+ return useLimitState;
26
+ },
27
+ useQueryState: function() {
20
28
  return useQueryState;
21
29
  }
22
30
  });
@@ -51,3 +59,36 @@ function useQueryState(props) {
51
59
  handleQueryBlur
52
60
  };
53
61
  }
62
+ function useLimitState(props) {
63
+ const { onChange, value } = props;
64
+ // TODO: reusable hook or helper util instead of duplicating from useQueryState
65
+ const [limit, setLimit] = (0, _react.useState)(value.limit ? value.limit.toString() : '');
66
+ const [lastSyncedLimit, setLastSyncedLimit] = (0, _react.useState)(value.limit);
67
+ if (value.limit !== lastSyncedLimit) {
68
+ setLimit(value.limit ? value.limit.toString() : '');
69
+ setLastSyncedLimit(value.limit);
70
+ }
71
+ // limit must be empty or an integer > 0
72
+ const limitHasError = !(limit === '' || /^[0-9]+$/.test(limit) && parseInt(limit) > 0);
73
+ // Update our local state as the user types
74
+ const handleLimitChange = (e)=>{
75
+ setLimit(e);
76
+ };
77
+ // Propagate changes to the panel preview component when limit TextField is blurred
78
+ const handleLimitBlur = ()=>{
79
+ if (limitHasError) {
80
+ return;
81
+ }
82
+ const limitVal = limit === '' ? undefined : parseInt(limit);
83
+ setLastSyncedLimit(limitVal);
84
+ onChange((0, _immer.produce)(value, (draft)=>{
85
+ draft.limit = limitVal;
86
+ }));
87
+ };
88
+ return {
89
+ limit,
90
+ handleLimitChange,
91
+ handleLimitBlur,
92
+ limitHasError
93
+ };
94
+ }
@@ -43,6 +43,12 @@ _export(exports, {
43
43
  return MOCK_TRACE_RESPONSE_SMALL;
44
44
  }
45
45
  });
46
+ function addParentReferences(span) {
47
+ for (const child of span.childSpans){
48
+ child.parentSpan = span;
49
+ addParentReferences(child);
50
+ }
51
+ }
46
52
  const MOCK_TRACE_RESPONSE = {
47
53
  batches: [
48
54
  {
@@ -2452,159 +2458,175 @@ const MOCK_TRACE_DATA_SEARCHRESULT = {
2452
2458
  executedQueryString: 'duration > 900ms'
2453
2459
  }
2454
2460
  };
2455
- const shopBackendResource = {
2456
- serviceName: 'shop-backend',
2457
- attributes: [
2458
- {
2459
- key: 'service.name',
2460
- value: {
2461
- stringValue: 'shop-backend'
2462
- }
2463
- }
2464
- ]
2465
- };
2466
- const authServiceResource = {
2467
- serviceName: 'auth-service',
2468
- attributes: [
2469
- {
2470
- key: 'service.name',
2471
- value: {
2472
- stringValue: 'auth-service'
2473
- }
2474
- }
2475
- ]
2476
- };
2477
- const k6scope = {
2478
- name: 'k6'
2479
- };
2480
- const span4 = {
2481
- resource: authServiceResource,
2482
- scope: k6scope,
2483
- childSpans: [],
2484
- traceId: '+9N4RSCdQ83M1BjcX5/wIQ==',
2485
- spanId: 'AYBIFdTlycc=',
2486
- parentSpanId: 'hGe8oRN3wWY=',
2487
- name: 'authenticate',
2488
- kind: 'SPAN_KIND_SERVER',
2489
- startTimeUnixMs: 1718122135970.602,
2490
- endTimeUnixMs: 1718122136107.7405,
2491
- attributes: [],
2492
- events: [],
2493
- status: {
2494
- message: 'Forbidden',
2495
- code: 'STATUS_CODE_ERROR'
2496
- }
2497
- };
2498
- const span3 = {
2499
- resource: shopBackendResource,
2500
- scope: k6scope,
2501
- childSpans: [],
2502
- traceId: '+9N4RSCdQ83M1BjcX5/wIQ==',
2503
- spanId: 'r2NxHHPjciM=',
2504
- parentSpanId: 'nCLrd8tcFMc=',
2505
- name: 'get-article',
2506
- kind: 'SPAN_KIND_CLIENT',
2507
- startTimeUnixMs: 1718122135965.2107,
2508
- endTimeUnixMs: 1718122136520.158,
2509
- attributes: [
2510
- {
2511
- key: 'http.method',
2512
- value: {
2513
- stringValue: 'DELETE'
2514
- }
2515
- }
2516
- ],
2517
- events: [],
2518
- status: {}
2519
- };
2520
- const span2 = {
2521
- resource: shopBackendResource,
2522
- scope: k6scope,
2523
- childSpans: [
2524
- span4
2525
- ],
2526
- traceId: '+9N4RSCdQ83M1BjcX5/wIQ==',
2527
- spanId: 'hGe8oRN3wWY=',
2528
- parentSpanId: 'nCLrd8tcFMc=',
2529
- name: 'authenticate',
2530
- kind: 'SPAN_KIND_CLIENT',
2531
- startTimeUnixMs: 1718122135954.7656,
2532
- endTimeUnixMs: 1718122136154.2273,
2533
- attributes: [
2534
- {
2535
- key: 'net.transport',
2536
- value: {
2537
- stringValue: 'ip_tcp'
2538
- }
2539
- }
2540
- ],
2541
- events: [
2542
- {
2543
- timeUnixMs: 1718122136068.6138,
2544
- name: 'event_k6.7W272ywYih',
2461
+ const MOCK_TRACE_DATA_TRACE = {
2462
+ trace: {
2463
+ rootSpan: {
2464
+ resource: {
2465
+ serviceName: 'shop-backend',
2466
+ attributes: [
2467
+ {
2468
+ key: 'service.name',
2469
+ value: {
2470
+ stringValue: 'shop-backend'
2471
+ }
2472
+ }
2473
+ ]
2474
+ },
2475
+ scope: {
2476
+ name: 'k6'
2477
+ },
2478
+ traceId: '+9N4RSCdQ83M1BjcX5/wIQ==',
2479
+ spanId: 'nCLrd8tcFMc=',
2480
+ name: 'article-to-cart',
2481
+ kind: 'SPAN_KIND_SERVER',
2482
+ startTimeUnixMs: 1718122135898.4426,
2483
+ endTimeUnixMs: 1718122136696.6519,
2545
2484
  attributes: [
2546
2485
  {
2547
- key: 'k6.sJekvnAr5h7vJfK',
2486
+ key: 'http.url',
2548
2487
  value: {
2549
- stringValue: 'SZ9vwpLkAzAm2Bju1VJroUlGD8u3pS'
2488
+ stringValue: 'https://shop-backend.local:8523/article-to-cart'
2550
2489
  }
2551
2490
  },
2552
2491
  {
2553
- key: 'k6.81JLO5NlT1lAeF1',
2492
+ key: 'http.status_code',
2554
2493
  value: {
2555
- stringValue: '4AzrPTOML11aIN3dYgbKSaAe9HErnZ'
2494
+ intValue: '202'
2556
2495
  }
2496
+ }
2497
+ ],
2498
+ events: [],
2499
+ status: {},
2500
+ childSpans: [
2501
+ {
2502
+ resource: {
2503
+ serviceName: 'shop-backend',
2504
+ attributes: [
2505
+ {
2506
+ key: 'service.name',
2507
+ value: {
2508
+ stringValue: 'shop-backend'
2509
+ }
2510
+ }
2511
+ ]
2512
+ },
2513
+ scope: {
2514
+ name: 'k6'
2515
+ },
2516
+ childSpans: [
2517
+ {
2518
+ resource: {
2519
+ serviceName: 'auth-service',
2520
+ attributes: [
2521
+ {
2522
+ key: 'service.name',
2523
+ value: {
2524
+ stringValue: 'auth-service'
2525
+ }
2526
+ }
2527
+ ]
2528
+ },
2529
+ scope: {
2530
+ name: 'k6'
2531
+ },
2532
+ childSpans: [],
2533
+ traceId: '+9N4RSCdQ83M1BjcX5/wIQ==',
2534
+ spanId: 'AYBIFdTlycc=',
2535
+ parentSpanId: 'hGe8oRN3wWY=',
2536
+ name: 'authenticate',
2537
+ kind: 'SPAN_KIND_SERVER',
2538
+ startTimeUnixMs: 1718122135970.602,
2539
+ endTimeUnixMs: 1718122136107.7405,
2540
+ attributes: [],
2541
+ events: [],
2542
+ status: {
2543
+ message: 'Forbidden',
2544
+ code: 'STATUS_CODE_ERROR'
2545
+ }
2546
+ }
2547
+ ],
2548
+ traceId: '+9N4RSCdQ83M1BjcX5/wIQ==',
2549
+ spanId: 'hGe8oRN3wWY=',
2550
+ parentSpanId: 'nCLrd8tcFMc=',
2551
+ name: 'authenticate',
2552
+ kind: 'SPAN_KIND_CLIENT',
2553
+ startTimeUnixMs: 1718122135954.7656,
2554
+ endTimeUnixMs: 1718122136154.2273,
2555
+ attributes: [
2556
+ {
2557
+ key: 'net.transport',
2558
+ value: {
2559
+ stringValue: 'ip_tcp'
2560
+ }
2561
+ }
2562
+ ],
2563
+ events: [
2564
+ {
2565
+ timeUnixMs: 1718122136068.6138,
2566
+ name: 'event_k6.7W272ywYih',
2567
+ attributes: [
2568
+ {
2569
+ key: 'k6.sJekvnAr5h7vJfK',
2570
+ value: {
2571
+ stringValue: 'SZ9vwpLkAzAm2Bju1VJroUlGD8u3pS'
2572
+ }
2573
+ },
2574
+ {
2575
+ key: 'k6.81JLO5NlT1lAeF1',
2576
+ value: {
2577
+ stringValue: '4AzrPTOML11aIN3dYgbKSaAe9HErnZ'
2578
+ }
2579
+ },
2580
+ {
2581
+ key: 'k6.tdj0bfOxLndyJuN',
2582
+ value: {
2583
+ stringValue: 'PeMAsdQ5469IjQgGtifBA7OgfFdoMb'
2584
+ }
2585
+ }
2586
+ ]
2587
+ }
2588
+ ],
2589
+ status: {}
2557
2590
  },
2558
2591
  {
2559
- key: 'k6.tdj0bfOxLndyJuN',
2560
- value: {
2561
- stringValue: 'PeMAsdQ5469IjQgGtifBA7OgfFdoMb'
2562
- }
2592
+ resource: {
2593
+ serviceName: 'shop-backend',
2594
+ attributes: [
2595
+ {
2596
+ key: 'service.name',
2597
+ value: {
2598
+ stringValue: 'shop-backend'
2599
+ }
2600
+ }
2601
+ ]
2602
+ },
2603
+ scope: {
2604
+ name: 'k6'
2605
+ },
2606
+ childSpans: [],
2607
+ traceId: '+9N4RSCdQ83M1BjcX5/wIQ==',
2608
+ spanId: 'r2NxHHPjciM=',
2609
+ parentSpanId: 'nCLrd8tcFMc=',
2610
+ name: 'get-article',
2611
+ kind: 'SPAN_KIND_CLIENT',
2612
+ startTimeUnixMs: 1718122135965.2107,
2613
+ endTimeUnixMs: 1718122136520.158,
2614
+ attributes: [
2615
+ {
2616
+ key: 'http.method',
2617
+ value: {
2618
+ stringValue: 'DELETE'
2619
+ }
2620
+ }
2621
+ ],
2622
+ events: [],
2623
+ status: {}
2563
2624
  }
2564
2625
  ]
2565
2626
  }
2566
- ],
2567
- status: {}
2568
- };
2569
- const span1 = {
2570
- resource: shopBackendResource,
2571
- scope: k6scope,
2572
- childSpans: [
2573
- span2,
2574
- span3
2575
- ],
2576
- traceId: '+9N4RSCdQ83M1BjcX5/wIQ==',
2577
- spanId: 'nCLrd8tcFMc=',
2578
- parentSpanId: undefined,
2579
- name: 'article-to-cart',
2580
- kind: 'SPAN_KIND_SERVER',
2581
- startTimeUnixMs: 1718122135898.4426,
2582
- endTimeUnixMs: 1718122136696.6519,
2583
- attributes: [
2584
- {
2585
- key: 'http.url',
2586
- value: {
2587
- stringValue: 'https://shop-backend.local:8523/article-to-cart'
2588
- }
2589
- },
2590
- {
2591
- key: 'http.status_code',
2592
- value: {
2593
- intValue: '202'
2594
- }
2595
- }
2596
- ],
2597
- events: [],
2598
- status: {}
2599
- };
2600
- span4.parentSpan = span2;
2601
- span2.parentSpan = span1;
2602
- span3.parentSpan = span1;
2603
- const MOCK_TRACE_DATA_TRACE = {
2604
- trace: {
2605
- rootSpan: span1
2606
2627
  },
2607
2628
  metadata: {
2608
2629
  executedQueryString: '61a1487c461d9e08'
2609
2630
  }
2610
2631
  };
2632
+ addParentReferences(MOCK_TRACE_DATA_TRACE.trace.rootSpan);