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

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 (53) hide show
  1. package/dist/cjs/components/TraceQLEditor.js +57 -16
  2. package/dist/cjs/components/complete.js +27 -8
  3. package/dist/cjs/index.js +1 -2
  4. package/dist/cjs/model/index.js +1 -1
  5. package/dist/cjs/plugins/tempo-trace-query/TempoTraceQuery.js +1 -0
  6. package/dist/cjs/plugins/tempo-trace-query/TempoTraceQueryEditor.js +48 -9
  7. package/dist/cjs/plugins/tempo-trace-query/get-trace-data.js +12 -12
  8. package/dist/cjs/plugins/tempo-trace-query/query-editor-model.js +44 -3
  9. package/dist/cjs/test/mock-data.js +160 -138
  10. package/dist/components/TraceQLEditor.d.ts.map +1 -1
  11. package/dist/components/TraceQLEditor.js +59 -18
  12. package/dist/components/TraceQLEditor.js.map +1 -1
  13. package/dist/components/complete.d.ts +0 -1
  14. package/dist/components/complete.d.ts.map +1 -1
  15. package/dist/components/complete.js +27 -8
  16. package/dist/components/complete.js.map +1 -1
  17. package/dist/index.d.ts +1 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/model/api-types.d.ts +4 -0
  22. package/dist/model/api-types.d.ts.map +1 -1
  23. package/dist/model/api-types.js.map +1 -1
  24. package/dist/model/index.d.ts +5 -0
  25. package/dist/model/index.d.ts.map +1 -0
  26. package/dist/model/index.js +1 -1
  27. package/dist/model/index.js.map +1 -1
  28. package/dist/model/trace-query-model.d.ts +1 -0
  29. package/dist/model/trace-query-model.d.ts.map +1 -1
  30. package/dist/model/trace-query-model.js.map +1 -1
  31. package/dist/plugins/tempo-trace-query/TempoTraceQuery.d.ts +2 -1
  32. package/dist/plugins/tempo-trace-query/TempoTraceQuery.d.ts.map +1 -1
  33. package/dist/plugins/tempo-trace-query/TempoTraceQuery.js +1 -0
  34. package/dist/plugins/tempo-trace-query/TempoTraceQuery.js.map +1 -1
  35. package/dist/plugins/tempo-trace-query/TempoTraceQueryEditor.d.ts.map +1 -1
  36. package/dist/plugins/tempo-trace-query/TempoTraceQueryEditor.js +52 -13
  37. package/dist/plugins/tempo-trace-query/TempoTraceQueryEditor.js.map +1 -1
  38. package/dist/plugins/tempo-trace-query/get-trace-data.d.ts.map +1 -1
  39. package/dist/plugins/tempo-trace-query/get-trace-data.js +12 -12
  40. package/dist/plugins/tempo-trace-query/get-trace-data.js.map +1 -1
  41. package/dist/plugins/tempo-trace-query/query-editor-model.d.ts +9 -0
  42. package/dist/plugins/tempo-trace-query/query-editor-model.d.ts.map +1 -1
  43. package/dist/plugins/tempo-trace-query/query-editor-model.js +35 -0
  44. package/dist/plugins/tempo-trace-query/query-editor-model.js.map +1 -1
  45. package/dist/test/mock-data.d.ts.map +1 -1
  46. package/dist/test/mock-data.js +160 -138
  47. package/dist/test/mock-data.js.map +1 -1
  48. package/package.json +4 -4
  49. package/dist/cjs/plugins/tempo-trace-query/DashboardTempoTraceQueryEditor.js +0 -61
  50. package/dist/plugins/tempo-trace-query/DashboardTempoTraceQueryEditor.d.ts +0 -13
  51. package/dist/plugins/tempo-trace-query/DashboardTempoTraceQueryEditor.d.ts.map +0 -1
  52. package/dist/plugins/tempo-trace-query/DashboardTempoTraceQueryEditor.js +0 -53
  53. 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':
package/dist/cjs/index.js CHANGED
@@ -30,8 +30,7 @@ _export(exports, {
30
30
  });
31
31
  const _tempodatasource = require("./plugins/tempo-datasource");
32
32
  const _TempoTraceQuery = require("./plugins/tempo-trace-query/TempoTraceQuery");
33
- _export_star(require("./model/tempo-client"), exports);
34
- _export_star(require("./model/tempo-selectors"), exports);
33
+ _export_star(require("./model"), exports);
35
34
  _export_star(require("./components/TraceQLExtension"), exports);
36
35
  function _export_star(from, to) {
37
36
  Object.keys(from).forEach(function(k) {
@@ -17,7 +17,7 @@ Object.defineProperty(exports, "__esModule", {
17
17
  _export_star(require("./tempo-client"), exports);
18
18
  _export_star(require("./tempo-selectors"), exports);
19
19
  _export_star(require("./trace-query-model"), exports);
20
- _export_star(require("./types"), exports);
20
+ _export_star(require("./api-types"), exports);
21
21
  function _export_star(from, to) {
22
22
  Object.keys(from).forEach(function(k) {
23
23
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
@@ -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
+ }