@perses-dev/plugin-system 0.51.0-beta.1 → 0.51.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 (83) hide show
  1. package/dist/cjs/components/DatasourceSelect.js +163 -81
  2. package/dist/cjs/components/HTTPSettingsEditor/HTTPSettingsEditor.js +5 -2
  3. package/dist/cjs/components/MultiQueryEditor/MultiQueryEditor.js +1 -1
  4. package/dist/cjs/components/PanelSpecEditor/PanelSpecEditor.js +2 -2
  5. package/dist/cjs/components/TimeRangeControls/TimeRangeControls.js +91 -1
  6. package/dist/cjs/components/Variables/VariableEditorForm/VariableEditorForm.js +11 -7
  7. package/dist/cjs/components/Variables/variable-model.js +4 -2
  8. package/dist/cjs/constants/user-interface-text.js +3 -1
  9. package/dist/cjs/remote/PluginRuntime.js +168 -162
  10. package/dist/cjs/runtime/TimeRangeProvider/TimeRangeSettingsProvider.js +13 -0
  11. package/dist/cjs/runtime/time-series-queries.js +5 -14
  12. package/dist/cjs/runtime/trace-queries.js +46 -16
  13. package/dist/cjs/runtime/utils.js +39 -0
  14. package/dist/cjs/test/render-hook.js +31 -0
  15. package/dist/cjs/test/render.js +4 -21
  16. package/dist/cjs/test/utils.js +49 -0
  17. package/dist/components/DatasourceSelect.d.ts +20 -3
  18. package/dist/components/DatasourceSelect.d.ts.map +1 -1
  19. package/dist/components/DatasourceSelect.js +150 -74
  20. package/dist/components/DatasourceSelect.js.map +1 -1
  21. package/dist/components/HTTPSettingsEditor/HTTPSettingsEditor.d.ts.map +1 -1
  22. package/dist/components/HTTPSettingsEditor/HTTPSettingsEditor.js +5 -2
  23. package/dist/components/HTTPSettingsEditor/HTTPSettingsEditor.js.map +1 -1
  24. package/dist/components/MultiQueryEditor/MultiQueryEditor.js +1 -1
  25. package/dist/components/MultiQueryEditor/MultiQueryEditor.js.map +1 -1
  26. package/dist/components/PanelSpecEditor/PanelSpecEditor.js +2 -2
  27. package/dist/components/PanelSpecEditor/PanelSpecEditor.js.map +1 -1
  28. package/dist/components/PluginRegistry/PluginRegistry.js.map +1 -1
  29. package/dist/components/PluginRegistry/plugin-indexes.d.ts +1 -1
  30. package/dist/components/PluginRegistry/plugin-indexes.d.ts.map +1 -1
  31. package/dist/components/PluginRegistry/plugin-indexes.js.map +1 -1
  32. package/dist/components/TimeRangeControls/TimeRangeControls.d.ts +2 -1
  33. package/dist/components/TimeRangeControls/TimeRangeControls.d.ts.map +1 -1
  34. package/dist/components/TimeRangeControls/TimeRangeControls.js +94 -2
  35. package/dist/components/TimeRangeControls/TimeRangeControls.js.map +1 -1
  36. package/dist/components/Variables/VariableEditorForm/VariableEditorForm.d.ts.map +1 -1
  37. package/dist/components/Variables/VariableEditorForm/VariableEditorForm.js +11 -7
  38. package/dist/components/Variables/VariableEditorForm/VariableEditorForm.js.map +1 -1
  39. package/dist/components/Variables/variable-model.d.ts.map +1 -1
  40. package/dist/components/Variables/variable-model.js +4 -2
  41. package/dist/components/Variables/variable-model.js.map +1 -1
  42. package/dist/constants/user-interface-text.d.ts +2 -0
  43. package/dist/constants/user-interface-text.d.ts.map +1 -1
  44. package/dist/constants/user-interface-text.js +3 -1
  45. package/dist/constants/user-interface-text.js.map +1 -1
  46. package/dist/model/trace-queries.d.ts +13 -1
  47. package/dist/model/trace-queries.d.ts.map +1 -1
  48. package/dist/model/trace-queries.js.map +1 -1
  49. package/dist/remote/PluginRuntime.d.ts +0 -1
  50. package/dist/remote/PluginRuntime.d.ts.map +1 -1
  51. package/dist/remote/PluginRuntime.js +169 -160
  52. package/dist/remote/PluginRuntime.js.map +1 -1
  53. package/dist/runtime/TimeRangeProvider/TimeRangeSettingsProvider.d.ts +7 -0
  54. package/dist/runtime/TimeRangeProvider/TimeRangeSettingsProvider.d.ts.map +1 -1
  55. package/dist/runtime/TimeRangeProvider/TimeRangeSettingsProvider.js +13 -0
  56. package/dist/runtime/TimeRangeProvider/TimeRangeSettingsProvider.js.map +1 -1
  57. package/dist/runtime/plugin-registry.d.ts +2 -2
  58. package/dist/runtime/plugin-registry.d.ts.map +1 -1
  59. package/dist/runtime/plugin-registry.js.map +1 -1
  60. package/dist/runtime/time-series-queries.d.ts +1 -1
  61. package/dist/runtime/time-series-queries.d.ts.map +1 -1
  62. package/dist/runtime/time-series-queries.js +4 -13
  63. package/dist/runtime/time-series-queries.js.map +1 -1
  64. package/dist/runtime/trace-queries.d.ts.map +1 -1
  65. package/dist/runtime/trace-queries.js +47 -17
  66. package/dist/runtime/trace-queries.js.map +1 -1
  67. package/dist/runtime/utils.d.ts +7 -0
  68. package/dist/runtime/utils.d.ts.map +1 -0
  69. package/dist/runtime/utils.js +25 -0
  70. package/dist/runtime/utils.js.map +1 -0
  71. package/dist/test/render-hook.d.ts +8 -0
  72. package/dist/test/render-hook.d.ts.map +1 -0
  73. package/dist/test/render-hook.js +26 -0
  74. package/dist/test/render-hook.js.map +1 -0
  75. package/dist/test/render.d.ts +1 -5
  76. package/dist/test/render.d.ts.map +1 -1
  77. package/dist/test/render.js +4 -21
  78. package/dist/test/render.js.map +1 -1
  79. package/dist/test/utils.d.ts +9 -0
  80. package/dist/test/utils.d.ts.map +1 -0
  81. package/dist/test/utils.js +41 -0
  82. package/dist/test/utils.js.map +1 -0
  83. package/package.json +5 -5
@@ -26,23 +26,48 @@ _export(exports, {
26
26
  },
27
27
  DatasourceSelect: function() {
28
28
  return DatasourceSelect;
29
+ },
30
+ datasourceSelectValueToSelector: function() {
31
+ return datasourceSelectValueToSelector;
32
+ },
33
+ isVariableDatasource: function() {
34
+ return isVariableDatasource;
35
+ },
36
+ optionValueToSelector: function() {
37
+ return optionValueToSelector;
38
+ },
39
+ selectorToOptionValue: function() {
40
+ return selectorToOptionValue;
41
+ },
42
+ useDatasourceSelectValueToSelector: function() {
43
+ return useDatasourceSelectValueToSelector;
29
44
  }
30
45
  });
31
46
  const _jsxruntime = require("react/jsx-runtime");
47
+ const _react = require("react");
32
48
  const _OpenInNew = /*#__PURE__*/ _interop_require_default(require("mdi-material-ui/OpenInNew"));
33
49
  const _material = require("@mui/material");
34
- const _react = require("react");
35
50
  const _runtime = require("../runtime");
51
+ const _utils = require("../utils");
36
52
  function _interop_require_default(obj) {
37
53
  return obj && obj.__esModule ? obj : {
38
54
  default: obj
39
55
  };
40
56
  }
57
+ const DATASOURCE_VARIABLE_VALUE_PREFIX = '__DATASOURCE_VARIABLE_VALUE__';
58
+ const VARIABLE_IDENTIFIER = '$';
59
+ const emptyDatasourceOption = {
60
+ name: '',
61
+ value: ''
62
+ };
41
63
  function DatasourceSelect(props) {
42
64
  const { datasourcePluginKind, value, project, onChange, ...others } = props;
43
65
  const { data, isLoading } = (0, _runtime.useListDatasourceSelectItems)(datasourcePluginKind, project);
44
- // Rebuild the group of the value if not provided
66
+ const variables = (0, _runtime.useVariableValues)();
45
67
  const defaultValue = (0, _react.useMemo)(()=>{
68
+ if (isVariableDatasource(value)) {
69
+ return value;
70
+ }
46
71
  const group = (data ?? []).flatMap((itemGroup)=>itemGroup.items).find((item)=>{
47
72
  return value.kind === item.selector.kind && value.name === item.selector.name && !item.overridden;
48
73
  })?.selector.group;
@@ -54,87 +79,112 @@ function DatasourceSelect(props) {
54
79
  value,
55
80
  data
56
81
  ]);
57
- // Convert the datasource list into menu items with name/group?/value strings that the Select input can work with
58
- const menuItems = (0, _react.useMemo)(()=>{
59
- return (data ?? []).map((itemGroup)=>({
60
- group: itemGroup.group,
61
- editLink: itemGroup.editLink,
62
- items: itemGroup.items.map((item)=>({
63
- name: item.name,
64
- overriding: item.overriding,
65
- overridden: item.overridden,
66
- saved: item.saved ?? true,
67
- group: item.selector.group,
68
- value: selectorToOptionValue(item.selector)
69
- }))
70
- }));
82
+ const options = (0, _react.useMemo)(()=>{
83
+ const datasourceOptions = (data || []).flatMap((itemGroup)=>itemGroup.items.map((item)=>({
84
+ groupLabel: itemGroup.group,
85
+ groupEditLink: itemGroup.editLink,
86
+ name: item.name,
87
+ overriding: item.overriding,
88
+ overridden: item.overridden,
89
+ saved: item.saved ?? true,
90
+ group: item.selector.group,
91
+ value: selectorToOptionValue(item.selector)
92
+ })));
93
+ const datasourceOptionsMap = new Map(datasourceOptions.map((option)=>[
94
+ option.name,
95
+ option
96
+ ]));
97
+ const variableOptions = Object.entries(variables).flatMap(([name, variable])=>{
98
+ if (Array.isArray(variable.value)) return [];
99
+ const associatedDatasource = datasourceOptionsMap.get(variable.value ?? '');
100
+ if (!associatedDatasource) return [];
101
+ return {
102
+ groupLabel: 'Variables',
103
+ name: `${VARIABLE_IDENTIFIER}${name}`,
104
+ saved: true,
105
+ value: `${DATASOURCE_VARIABLE_VALUE_PREFIX}${VARIABLE_IDENTIFIER}${name}`
106
+ };
107
+ });
108
+ return [
109
+ ...datasourceOptions,
110
+ ...variableOptions
111
+ ];
71
112
  }, [
72
- data
113
+ data,
114
+ variables
73
115
  ]);
74
- // While loading available values, just use an empty string so MUI select doesn't warn about values out of range
75
- const optionValue = isLoading ? '' : selectorToOptionValue(defaultValue);
116
+ // While loading available values, just use an empty datasource option so MUI select doesn't warn about values out of range
117
+ const optionValue = isLoading ? emptyDatasourceOption : options.find((option)=>option.value === selectorToOptionValue(defaultValue));
76
118
  // When the user makes a selection, convert the string option value back to a DatasourceSelector
77
- const handleChange = (e)=>{
78
- const next = optionValueToSelector(e.target.value);
79
- onChange(next);
119
+ const handleChange = (selectedOption)=>{
120
+ if (selectedOption) {
121
+ const next = optionValueToSelector(selectedOption?.value || '');
122
+ onChange(next);
123
+ } else {
124
+ onChange({
125
+ kind: datasourcePluginKind
126
+ });
127
+ }
80
128
  };
81
129
  // We use a fake action event when we click on the action of the chip (hijack the "delete" feature).
82
130
  // This is because the href link action is on the `deleteIcon` property already, but the `onDelete` property
83
131
  // controls its visibility.
84
132
  // eslint-disable-next-line @typescript-eslint/no-empty-function
85
133
  const fakeActionEvent = ()=>{};
86
- // TODO:
87
- // - Does this need a loading indicator of some kind?
88
- // - The group's edit link is not clickable once selected.
89
- // - The group's edit link is disabled if datasource is overridden.
90
- // Ref: https://github.com/mui/material-ui/issues/36572
91
- return /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Select, {
92
- ...others,
134
+ return /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Autocomplete, {
135
+ options: options,
136
+ renderInput: (params)=>/*#__PURE__*/ (0, _jsxruntime.jsx)(_material.TextField, {
137
+ ...params,
138
+ label: others.label,
139
+ placeholder: ""
140
+ }),
141
+ groupBy: (option)=>option.groupLabel || 'No group',
142
+ getOptionLabel: (option)=>{
143
+ return option.name;
144
+ },
145
+ onChange: (_, v)=>handleChange(v),
93
146
  value: optionValue,
94
- onChange: handleChange,
95
- children: menuItems.map((menuItemGroup)=>[
96
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Divider, {}, `${menuItemGroup.group}-divider`),
97
- ...menuItemGroup.items.map((menuItem)=>/*#__PURE__*/ (0, _jsxruntime.jsx)(_material.MenuItem, {
98
- value: menuItem.value,
99
- disabled: menuItem.overridden || !menuItem.saved,
100
- children: /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Stack, {
101
- direction: "row",
102
- alignItems: "center",
103
- justifyContent: "space-between",
104
- width: "100%",
105
- children: [
106
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.ListItemText, {
107
- children: /*#__PURE__*/ (0, _jsxruntime.jsx)(DatasourceName, {
108
- name: menuItem.name,
109
- overridden: menuItem.overridden,
110
- overriding: menuItem.overriding
111
- })
112
- }),
113
- !menuItem.saved && /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.ListItemText, {
114
- children: "Save the dashboard to enable this datasource"
115
- }),
116
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.ListItemText, {
117
- style: {
118
- textAlign: 'right'
119
- },
120
- children: menuItemGroup.group && menuItemGroup.group.length > 0 && /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Chip, {
121
- disabled: false,
122
- label: menuItemGroup.group,
123
- size: "small",
124
- onDelete: menuItemGroup.editLink ? fakeActionEvent : undefined,
125
- deleteIcon: menuItemGroup.editLink ? /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.IconButton, {
126
- href: menuItemGroup.editLink,
127
- target: "_blank",
128
- children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_OpenInNew.default, {
129
- fontSize: "small"
130
- })
131
- }) : undefined
132
- })
147
+ renderOption: (props, option)=>{
148
+ return /*#__PURE__*/ (0, _react.createElement)("li", {
149
+ ...props,
150
+ key: option.value
151
+ }, /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Stack, {
152
+ direction: "row",
153
+ alignItems: "center",
154
+ justifyContent: "space-between",
155
+ width: "100%",
156
+ children: [
157
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.ListItemText, {
158
+ children: /*#__PURE__*/ (0, _jsxruntime.jsx)(DatasourceName, {
159
+ name: option.name,
160
+ overridden: option.overridden,
161
+ overriding: option.overriding
162
+ })
163
+ }),
164
+ !option.saved && /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.ListItemText, {
165
+ children: "Save the dashboard to enable this datasource"
166
+ }),
167
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.ListItemText, {
168
+ style: {
169
+ textAlign: 'right'
170
+ },
171
+ children: option.groupLabel && option.groupLabel.length > 0 && /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Chip, {
172
+ disabled: false,
173
+ label: option.groupLabel,
174
+ size: "small",
175
+ onDelete: option.groupEditLink ? fakeActionEvent : undefined,
176
+ deleteIcon: option.groupEditLink ? /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.IconButton, {
177
+ href: option.groupEditLink,
178
+ target: "_blank",
179
+ children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_OpenInNew.default, {
180
+ fontSize: "small"
133
181
  })
134
- ]
182
+ }) : undefined
135
183
  })
136
- }, menuItem.value))
137
- ])
184
+ })
185
+ ]
186
+ }));
187
+ }
138
188
  });
139
189
  }
140
190
  function DatasourceName(props) {
@@ -154,22 +204,20 @@ function DatasourceName(props) {
154
204
  }
155
205
  // Delimiter used to stringify/parse option values
156
206
  const OPTION_VALUE_DELIMITER = '_____';
157
- /**
158
- * Given a DatasourceSelectItemSelector,
159
- * returns a string value like `{kind}_____{group}_____{name}` that can be used as a Select input value.
160
- * @param selector
161
- */ function selectorToOptionValue(selector) {
207
+ function selectorToOptionValue(selector) {
208
+ if (isVariableDatasource(selector)) {
209
+ return `${DATASOURCE_VARIABLE_VALUE_PREFIX}${selector}`;
210
+ }
162
211
  return [
163
212
  selector.kind,
164
213
  selector.group ?? '',
165
214
  selector.name ?? ''
166
215
  ].join(OPTION_VALUE_DELIMITER);
167
216
  }
168
- /**
169
- * Given an option value name like `{kind}_____{group}_____{name}`,
170
- * returns a DatasourceSelector to be used by the query data model.
171
- * @param optionValue
172
- */ function optionValueToSelector(optionValue) {
217
+ function optionValueToSelector(optionValue) {
218
+ if (optionValue.startsWith(DATASOURCE_VARIABLE_VALUE_PREFIX)) {
219
+ return optionValue.split(DATASOURCE_VARIABLE_VALUE_PREFIX)[1];
220
+ }
173
221
  const words = optionValue.split(OPTION_VALUE_DELIMITER);
174
222
  const kind = words[0];
175
223
  const name = words[2];
@@ -181,3 +229,37 @@ const OPTION_VALUE_DELIMITER = '_____';
181
229
  name: name === '' ? undefined : name
182
230
  };
183
231
  }
232
+ function isVariableDatasource(value) {
233
+ return typeof value === 'string' && value.startsWith(VARIABLE_IDENTIFIER);
234
+ }
235
+ const datasourceSelectValueToSelector = (value, variables, datasourceSelectItemGroups)=>{
236
+ if (!isVariableDatasource(value)) {
237
+ return value;
238
+ }
239
+ const [variableName] = (0, _utils.parseVariables)(value);
240
+ const variable = variables[variableName ?? ''];
241
+ // If the variable is not defined or if its value is an array, we cannot determine a selector and return undefined
242
+ if (!variable || Array.isArray(variable.value)) {
243
+ return undefined;
244
+ }
245
+ const associatedDatasource = (datasourceSelectItemGroups || []).flatMap((itemGroup)=>itemGroup.items).find((datasource)=>datasource.name === variable.value);
246
+ // If the variable value is not a datasource, we cannot determine a selector and return undefined
247
+ if (associatedDatasource === undefined) {
248
+ return undefined;
249
+ }
250
+ const datasourceSelector = {
251
+ kind: associatedDatasource.selector.kind,
252
+ name: associatedDatasource.selector.name
253
+ };
254
+ return datasourceSelector;
255
+ };
256
+ const useDatasourceSelectValueToSelector = (value, datasourcePluginKind)=>{
257
+ const { data } = (0, _runtime.useListDatasourceSelectItems)(datasourcePluginKind);
258
+ const variables = (0, _runtime.useVariableValues)();
259
+ if (!isVariableDatasource(value)) {
260
+ return value;
261
+ }
262
+ return datasourceSelectValueToSelector(value, variables, data) ?? {
263
+ kind: datasourcePluginKind
264
+ };
265
+ };
@@ -78,6 +78,10 @@ function HTTPSettingsEditor(props) {
78
78
  const { value, onChange, isReadonly, initialSpecDirect, initialSpecProxy } = props;
79
79
  const strDirect = 'Direct access';
80
80
  const strProxy = 'Proxy';
81
+ // Initialize Proxy mode by default, if neither direct nor proxy mode is selected.
82
+ if (!value.directUrl && !value.proxy) {
83
+ Object.assign(value, initialSpecProxy);
84
+ }
81
85
  // utilitary function used for headers when renaming a property
82
86
  // -> TODO it would be cleaner to manipulate headers as an intermediary list instead, to avoid doing this.
83
87
  const buildNewHeaders = (oldHeaders, oldName, newName)=>{
@@ -487,8 +491,7 @@ function HTTPSettingsEditor(props) {
487
491
  // bug in case the tabs get eventually swapped in the future.
488
492
  const directModeId = tabs.findIndex((tab)=>tab.label === strDirect);
489
493
  const proxyModeId = tabs.findIndex((tab)=>tab.label === strProxy);
490
- // In "update datasource" case, set defaultTab to the mode that this datasource is currently relying on.
491
- // Otherwise (create datasource), set defaultTab to Direct access.
494
+ // Set defaultTab to the mode that this datasource is currently relying on.
492
495
  const defaultTab = value.proxy ? proxyModeId : directModeId;
493
496
  // For better user experience, save previous states in mind for both mode.
494
497
  // This avoids losing everything when the user changes their mind back.
@@ -43,7 +43,7 @@ function useDefaultQueryDefinition(queryTypes) {
43
43
  const { defaultPluginKinds } = (0, _runtime.usePluginRegistry)();
44
44
  const defaultQueryKind = defaultPluginKinds?.[defaultQueryType] ?? queryPlugins?.[0]?.spec.name ?? '';
45
45
  const { data: defaultQueryPlugin } = (0, _runtime.usePlugin)(defaultQueryType, defaultQueryKind, {
46
- throwOnError: true,
46
+ useErrorBoundary: true,
47
47
  enabled: true
48
48
  });
49
49
  // This default query definition is used if no query is provided initially or when we add a new query
@@ -29,14 +29,14 @@ const _MultiQueryEditor = require("../MultiQueryEditor");
29
29
  function PanelSpecEditor(props) {
30
30
  const { control, panelDefinition, onJSONChange, onQueriesChange, onPluginSpecChange } = props;
31
31
  const { kind } = panelDefinition.spec.plugin;
32
- const { data: plugin, isPending, error } = (0, _runtime.usePlugin)('Panel', kind);
32
+ const { data: plugin, isLoading, error } = (0, _runtime.usePlugin)('Panel', kind);
33
33
  if (error) {
34
34
  return /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.ErrorAlert, {
35
35
  error: error
36
36
  });
37
37
  }
38
38
  // TODO: Proper loading indicator
39
- if (isPending) {
39
+ if (isLoading) {
40
40
  return null;
41
41
  }
42
42
  if (plugin === undefined) {
@@ -30,8 +30,11 @@ _export(exports, {
30
30
  });
31
31
  const _jsxruntime = require("react/jsx-runtime");
32
32
  const _Refresh = /*#__PURE__*/ _interop_require_default(require("mdi-material-ui/Refresh"));
33
+ const _PlusCircleOutline = /*#__PURE__*/ _interop_require_default(require("mdi-material-ui/PlusCircleOutline"));
34
+ const _MinusCircleOutline = /*#__PURE__*/ _interop_require_default(require("mdi-material-ui/MinusCircleOutline"));
33
35
  const _material = require("@mui/material");
34
36
  const _components = require("@perses-dev/components");
37
+ const _core = require("@perses-dev/core");
35
38
  const _react = require("react");
36
39
  const _constants = require("../../constants");
37
40
  const _runtime = require("../../runtime");
@@ -79,9 +82,10 @@ const DEFAULT_REFRESH_INTERVAL_OPTIONS = [
79
82
  }
80
83
  ];
81
84
  const DEFAULT_HEIGHT = '34px';
82
- function TimeRangeControls({ heightPx, showTimeRangeSelector = true, showRefreshButton = true, showRefreshInterval = true, showCustomTimeRange, timePresets }) {
85
+ function TimeRangeControls({ heightPx, showTimeRangeSelector = true, showRefreshButton = true, showRefreshInterval = true, showCustomTimeRange, showZoomButtons = true, timePresets }) {
83
86
  const { timeRange, setTimeRange, refresh, refreshInterval, setRefreshInterval } = (0, _runtime.useTimeRange)();
84
87
  const showCustomTimeRangeValue = (0, _runtime.useShowCustomTimeRangeSetting)(showCustomTimeRange);
88
+ const showZoomInOutButtons = (0, _runtime.useShowZoomRangeSetting)(showZoomButtons);
85
89
  const timePresetsValue = (0, _runtime.useTimeRangeOptionsSetting)(timePresets);
86
90
  // Convert height to a string, then use the string for styling
87
91
  const height = heightPx === undefined ? DEFAULT_HEIGHT : `${heightPx}px`;
@@ -95,6 +99,70 @@ function TimeRangeControls({ heightPx, showTimeRangeSelector = true, showRefresh
95
99
  }, [
96
100
  setRefreshInterval
97
101
  ]);
102
+ const fromDurationToMillis = (strDuration)=>{
103
+ const duration = (0, _core.parseDurationString)(strDuration);
104
+ const millis = // eslint-disable-next-line prettier/prettier
105
+ ((duration.seconds ?? 0) + (duration.minutes ?? 0) * 60 + (duration.hours ?? 0) * 3600 + (duration.days ?? 0) * 86400 + (duration.weeks ?? 0) * 7 * 86400 + (duration.months ?? 0) * 30.436875 * 86400 + // avg month duration is ok for zoom purposes
106
+ (duration.years ?? 0) * 365.2425 * 86400) * // avg year duration is ok for zoom purposes
107
+ // eslint-disable-next-line prettier/prettier
108
+ 1000; // to milliseconds
109
+ return millis;
110
+ };
111
+ // Function to double current time range, adding 50% before current start and 50% after current end
112
+ const doubleTimeRange = ()=>{
113
+ let newStart, newEnd, extendEndsBy;
114
+ const now = new Date();
115
+ if (Object.hasOwn(timeRange, 'start')) {
116
+ // current range is absolute
117
+ const absVal = timeRange;
118
+ extendEndsBy = (absVal.end.getTime() - absVal.start.getTime()) / 2; // half it to add 50% before current start and after current end
119
+ newStart = new Date(absVal.start.getTime() - extendEndsBy);
120
+ newEnd = new Date(absVal.end.getTime() + extendEndsBy);
121
+ } else {
122
+ // current range is relative
123
+ const relVal = timeRange;
124
+ extendEndsBy = fromDurationToMillis(relVal.pastDuration) / 2;
125
+ newEnd = typeof relVal.end === 'undefined' ? now : new Date(relVal.end.getTime() + extendEndsBy);
126
+ newStart = new Date(newEnd.getTime() - extendEndsBy * 4);
127
+ }
128
+ if (newEnd.getTime() > now.getTime()) {
129
+ // if the new computed end is in the future
130
+ newEnd = now;
131
+ newStart.setTime(now.getTime() - extendEndsBy * 4);
132
+ }
133
+ if (newStart.getTime() < 1) {
134
+ newStart.setTime(1);
135
+ }
136
+ return {
137
+ start: newStart,
138
+ end: newEnd
139
+ };
140
+ };
141
+ // Function to half current time range, cutting 25% before current start and 25% after current end
142
+ const halfTimeRange = ()=>{
143
+ let newStart, newEnd;
144
+ if (Object.hasOwn(timeRange, 'start')) {
145
+ const absVal = timeRange;
146
+ const shrinkEndsBy = (absVal.end.getTime() - absVal.start.getTime()) / 4;
147
+ newStart = new Date(absVal.start.getTime() + shrinkEndsBy);
148
+ newEnd = new Date(absVal.end.getTime() - shrinkEndsBy);
149
+ } else {
150
+ const relVal = timeRange;
151
+ const shrinkEndsBy = fromDurationToMillis(relVal.pastDuration) / 4; // 25% of it to cut after current start and before current end
152
+ const endIsAbsolute = typeof relVal.end !== 'undefined';
153
+ newEnd = endIsAbsolute ? new Date(relVal.end.getTime() - shrinkEndsBy) : new Date();
154
+ newStart = new Date(newEnd.getTime() - shrinkEndsBy * 2);
155
+ }
156
+ if (newStart.getTime() >= newEnd.getTime() - 1000) {
157
+ newStart.setTime(newEnd.getTime() - 1000);
158
+ }
159
+ return {
160
+ start: newStart,
161
+ end: newEnd
162
+ };
163
+ };
164
+ const setHalfTimeRange = ()=>setTimeRange(halfTimeRange());
165
+ const setDoubleTimeRange = ()=>setTimeRange(doubleTimeRange());
98
166
  return /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Stack, {
99
167
  direction: "row",
100
168
  spacing: 1,
@@ -106,6 +174,28 @@ function TimeRangeControls({ heightPx, showTimeRangeSelector = true, showRefresh
106
174
  height: height,
107
175
  showCustomTimeRange: showCustomTimeRangeValue
108
176
  }),
177
+ showZoomInOutButtons && /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.InfoTooltip, {
178
+ description: _constants.TOOLTIP_TEXT.zoomOut,
179
+ children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.ToolbarIconButton, {
180
+ "aria-label": _constants.TOOLTIP_TEXT.zoomOut,
181
+ onClick: setDoubleTimeRange,
182
+ sx: {
183
+ height
184
+ },
185
+ children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_MinusCircleOutline.default, {})
186
+ })
187
+ }),
188
+ showZoomInOutButtons && /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.InfoTooltip, {
189
+ description: _constants.TOOLTIP_TEXT.zoomIn,
190
+ children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.ToolbarIconButton, {
191
+ "aria-label": _constants.TOOLTIP_TEXT.zoomIn,
192
+ onClick: setHalfTimeRange,
193
+ sx: {
194
+ height
195
+ },
196
+ children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_PlusCircleOutline.default, {})
197
+ })
198
+ }),
109
199
  showRefreshButton && /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.InfoTooltip, {
110
200
  description: _constants.TOOLTIP_TEXT.refresh,
111
201
  children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.ToolbarIconButton, {
@@ -150,10 +150,12 @@ function ListVariableEditorForm({ action, control }) {
150
150
  const refreshPreview = ()=>{
151
151
  setPreviewSpec(form.getValues());
152
152
  };
153
- const kind = (0, _reacthookform.useWatch)({
154
- control: control,
155
- name: 'spec.plugin.kind'
153
+ const plugin = (0, _reacthookform.useWatch)({
154
+ control,
155
+ name: 'spec.plugin'
156
156
  });
157
+ const kind = plugin?.kind;
158
+ const pluginSpec = plugin?.spec;
157
159
  const _allowAllValue = (0, _reacthookform.useWatch)({
158
160
  control: control,
159
161
  name: 'spec.allowAllValue'
@@ -203,7 +205,8 @@ function ListVariableEditorForm({ action, control }) {
203
205
  children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_reacthookform.Controller, {
204
206
  control: control,
205
207
  name: "spec.plugin",
206
- render: ({ field })=>/*#__PURE__*/ (0, _jsxruntime.jsx)(_PluginEditor.PluginEditor, {
208
+ render: ({ field })=>{
209
+ return /*#__PURE__*/ (0, _jsxruntime.jsx)(_PluginEditor.PluginEditor, {
207
210
  width: "100%",
208
211
  pluginTypes: [
209
212
  'Variable'
@@ -212,9 +215,9 @@ function ListVariableEditorForm({ action, control }) {
212
215
  value: {
213
216
  selection: {
214
217
  type: 'Variable',
215
- kind: field.value?.kind ?? 'StaticListVariable'
218
+ kind: kind ?? 'StaticListVariable'
216
219
  },
217
- spec: field.value?.spec ?? {
220
+ spec: pluginSpec ?? {
218
221
  values: []
219
222
  }
220
223
  },
@@ -225,7 +228,8 @@ function ListVariableEditorForm({ action, control }) {
225
228
  spec: v.spec
226
229
  });
227
230
  }
228
- })
231
+ });
232
+ }
229
233
  })
230
234
  })
231
235
  ]
@@ -73,11 +73,13 @@ function useListVariablePluginValues(definition) {
73
73
  };
74
74
  const spec = definition.spec.plugin.spec;
75
75
  const capturingRegexp = definition.spec.capturingRegexp !== undefined ? new RegExp(definition.spec.capturingRegexp, 'g') : undefined;
76
- let dependsOnVariables;
76
+ let dependsOnVariables = Object.keys(allVariables); // Default to all variables
77
77
  if (variablePlugin?.dependsOn) {
78
78
  const dependencies = variablePlugin.dependsOn(spec, variablePluginCtx);
79
- dependsOnVariables = dependencies.variables;
79
+ dependsOnVariables = dependencies.variables ? dependencies.variables : dependsOnVariables;
80
80
  }
81
+ // Exclude self variable to avoid circular dependency
82
+ dependsOnVariables = dependsOnVariables.filter((v)=>v !== definition.spec.name);
81
83
  const variables = (0, _runtime.useAllVariableValues)(dependsOnVariables);
82
84
  let waitToLoad = false;
83
85
  if (dependsOnVariables) {
@@ -26,5 +26,7 @@ const TOOLTIP_TEXT = {
26
26
  copyVariableValues: 'Copy values to clipboard',
27
27
  // Time range controls buttons
28
28
  refresh: 'Refresh',
29
- refreshInterval: 'Auto refresh interval'
29
+ refreshInterval: 'Auto refresh interval',
30
+ zoomIn: 'Zoom in',
31
+ zoomOut: 'Zoom out'
30
32
  };