@perses-dev/plugin-system 0.51.0-beta.1 → 0.51.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.
- package/dist/cjs/components/DatasourceSelect.js +155 -71
- package/dist/cjs/components/TimeRangeControls/TimeRangeControls.js +91 -1
- package/dist/cjs/components/Variables/VariableEditorForm/VariableEditorForm.js +11 -7
- package/dist/cjs/constants/user-interface-text.js +3 -1
- package/dist/cjs/remote/PluginRuntime.js +168 -162
- package/dist/cjs/runtime/TimeRangeProvider/TimeRangeSettingsProvider.js +13 -0
- package/dist/cjs/runtime/time-series-queries.js +3 -13
- package/dist/cjs/runtime/trace-queries.js +46 -16
- package/dist/cjs/runtime/utils.js +39 -0
- package/dist/components/DatasourceSelect.d.ts +8 -3
- package/dist/components/DatasourceSelect.d.ts.map +1 -1
- package/dist/components/DatasourceSelect.js +148 -72
- package/dist/components/DatasourceSelect.js.map +1 -1
- package/dist/components/PluginRegistry/PluginRegistry.js.map +1 -1
- package/dist/components/PluginRegistry/plugin-indexes.d.ts +1 -1
- package/dist/components/PluginRegistry/plugin-indexes.d.ts.map +1 -1
- package/dist/components/PluginRegistry/plugin-indexes.js.map +1 -1
- package/dist/components/TimeRangeControls/TimeRangeControls.d.ts +2 -1
- package/dist/components/TimeRangeControls/TimeRangeControls.d.ts.map +1 -1
- package/dist/components/TimeRangeControls/TimeRangeControls.js +94 -2
- package/dist/components/TimeRangeControls/TimeRangeControls.js.map +1 -1
- package/dist/components/Variables/VariableEditorForm/VariableEditorForm.d.ts.map +1 -1
- package/dist/components/Variables/VariableEditorForm/VariableEditorForm.js +11 -7
- package/dist/components/Variables/VariableEditorForm/VariableEditorForm.js.map +1 -1
- package/dist/constants/user-interface-text.d.ts +2 -0
- package/dist/constants/user-interface-text.d.ts.map +1 -1
- package/dist/constants/user-interface-text.js +3 -1
- package/dist/constants/user-interface-text.js.map +1 -1
- package/dist/model/trace-queries.d.ts +13 -1
- package/dist/model/trace-queries.d.ts.map +1 -1
- package/dist/model/trace-queries.js.map +1 -1
- package/dist/remote/PluginRuntime.d.ts +0 -1
- package/dist/remote/PluginRuntime.d.ts.map +1 -1
- package/dist/remote/PluginRuntime.js +169 -160
- package/dist/remote/PluginRuntime.js.map +1 -1
- package/dist/runtime/TimeRangeProvider/TimeRangeSettingsProvider.d.ts +7 -0
- package/dist/runtime/TimeRangeProvider/TimeRangeSettingsProvider.d.ts.map +1 -1
- package/dist/runtime/TimeRangeProvider/TimeRangeSettingsProvider.js +13 -0
- package/dist/runtime/TimeRangeProvider/TimeRangeSettingsProvider.js.map +1 -1
- package/dist/runtime/plugin-registry.d.ts +2 -2
- package/dist/runtime/plugin-registry.d.ts.map +1 -1
- package/dist/runtime/plugin-registry.js.map +1 -1
- package/dist/runtime/time-series-queries.d.ts.map +1 -1
- package/dist/runtime/time-series-queries.js +1 -11
- package/dist/runtime/time-series-queries.js.map +1 -1
- package/dist/runtime/trace-queries.d.ts.map +1 -1
- package/dist/runtime/trace-queries.js +47 -17
- package/dist/runtime/trace-queries.js.map +1 -1
- package/dist/runtime/utils.d.ts +7 -0
- package/dist/runtime/utils.d.ts.map +1 -0
- package/dist/runtime/utils.js +25 -0
- package/dist/runtime/utils.js.map +1 -0
- package/package.json +3 -3
|
@@ -11,18 +11,29 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
14
|
+
import { createElement as _createElement } from "react";
|
|
14
15
|
import OpenInNewIcon from 'mdi-material-ui/OpenInNew';
|
|
15
|
-
import {
|
|
16
|
+
import { Stack, ListItemText, Chip, IconButton, Box, Autocomplete, TextField } from '@mui/material';
|
|
16
17
|
import { useMemo } from 'react';
|
|
17
|
-
import { useListDatasourceSelectItems } from '../runtime';
|
|
18
|
+
import { useListDatasourceSelectItems, useVariableValues } from '../runtime';
|
|
19
|
+
import { parseVariables } from '../utils';
|
|
20
|
+
const DATASOURCE_VARIABLE_VALUE_PREFIX = '__DATASOURCE_VARIABLE_VALUE__';
|
|
21
|
+
const VARIABLE_IDENTIFIER = '$';
|
|
22
|
+
const emptyDatasourceOption = {
|
|
23
|
+
name: '',
|
|
24
|
+
value: ''
|
|
25
|
+
};
|
|
18
26
|
/**
|
|
19
27
|
* Displays a MUI input for selecting a Datasource of a particular kind. Note: The 'value' and `onChange` handler for
|
|
20
28
|
* the input deal with a `DatasourceSelector`.
|
|
21
29
|
*/ export function DatasourceSelect(props) {
|
|
22
30
|
const { datasourcePluginKind, value, project, onChange, ...others } = props;
|
|
23
31
|
const { data, isLoading } = useListDatasourceSelectItems(datasourcePluginKind, project);
|
|
24
|
-
|
|
32
|
+
const variables = useVariableValues();
|
|
25
33
|
const defaultValue = useMemo(()=>{
|
|
34
|
+
if (isVariableDatasource(value)) {
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
26
37
|
const group = (data ?? []).flatMap((itemGroup)=>itemGroup.items).find((item)=>{
|
|
27
38
|
return value.kind === item.selector.kind && value.name === item.selector.name && !item.overridden;
|
|
28
39
|
})?.selector.group;
|
|
@@ -34,87 +45,112 @@ import { useListDatasourceSelectItems } from '../runtime';
|
|
|
34
45
|
value,
|
|
35
46
|
data
|
|
36
47
|
]);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
const options = useMemo(()=>{
|
|
49
|
+
const datasourceOptions = (data || []).flatMap((itemGroup)=>itemGroup.items.map((item)=>({
|
|
50
|
+
groupLabel: itemGroup.group,
|
|
51
|
+
groupEditLink: itemGroup.editLink,
|
|
52
|
+
name: item.name,
|
|
53
|
+
overriding: item.overriding,
|
|
54
|
+
overridden: item.overridden,
|
|
55
|
+
saved: item.saved ?? true,
|
|
56
|
+
group: item.selector.group,
|
|
57
|
+
value: selectorToOptionValue(item.selector)
|
|
58
|
+
})));
|
|
59
|
+
const datasourceOptionsMap = new Map(datasourceOptions.map((option)=>[
|
|
60
|
+
option.name,
|
|
61
|
+
option
|
|
62
|
+
]));
|
|
63
|
+
const variableOptions = Object.entries(variables).flatMap(([name, variable])=>{
|
|
64
|
+
if (Array.isArray(variable.value)) return [];
|
|
65
|
+
const associatedDatasource = datasourceOptionsMap.get(variable.value ?? '');
|
|
66
|
+
if (!associatedDatasource) return [];
|
|
67
|
+
return {
|
|
68
|
+
groupLabel: 'Variables',
|
|
69
|
+
name: `${VARIABLE_IDENTIFIER}${name}`,
|
|
70
|
+
saved: true,
|
|
71
|
+
value: `${DATASOURCE_VARIABLE_VALUE_PREFIX}${VARIABLE_IDENTIFIER}${name}`
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
return [
|
|
75
|
+
...datasourceOptions,
|
|
76
|
+
...variableOptions
|
|
77
|
+
];
|
|
51
78
|
}, [
|
|
52
|
-
data
|
|
79
|
+
data,
|
|
80
|
+
variables
|
|
53
81
|
]);
|
|
54
|
-
// While loading available values, just use an empty
|
|
55
|
-
const optionValue = isLoading ?
|
|
82
|
+
// While loading available values, just use an empty datasource option so MUI select doesn't warn about values out of range
|
|
83
|
+
const optionValue = isLoading ? emptyDatasourceOption : options.find((option)=>option.value === selectorToOptionValue(defaultValue));
|
|
56
84
|
// When the user makes a selection, convert the string option value back to a DatasourceSelector
|
|
57
|
-
const handleChange = (
|
|
58
|
-
|
|
59
|
-
|
|
85
|
+
const handleChange = (selectedOption)=>{
|
|
86
|
+
if (selectedOption) {
|
|
87
|
+
const next = optionValueToSelector(selectedOption?.value || '');
|
|
88
|
+
onChange(next);
|
|
89
|
+
} else {
|
|
90
|
+
onChange({
|
|
91
|
+
kind: datasourcePluginKind
|
|
92
|
+
});
|
|
93
|
+
}
|
|
60
94
|
};
|
|
61
95
|
// We use a fake action event when we click on the action of the chip (hijack the "delete" feature).
|
|
62
96
|
// This is because the href link action is on the `deleteIcon` property already, but the `onDelete` property
|
|
63
97
|
// controls its visibility.
|
|
64
98
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
65
99
|
const fakeActionEvent = ()=>{};
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
100
|
+
return /*#__PURE__*/ _jsx(Autocomplete, {
|
|
101
|
+
options: options,
|
|
102
|
+
renderInput: (params)=>/*#__PURE__*/ _jsx(TextField, {
|
|
103
|
+
...params,
|
|
104
|
+
label: others.label,
|
|
105
|
+
placeholder: ""
|
|
106
|
+
}),
|
|
107
|
+
groupBy: (option)=>option.groupLabel || 'No group',
|
|
108
|
+
getOptionLabel: (option)=>{
|
|
109
|
+
return option.name;
|
|
110
|
+
},
|
|
111
|
+
onChange: (_, v)=>handleChange(v),
|
|
73
112
|
value: optionValue,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
children: /*#__PURE__*/ _jsx(OpenInNewIcon, {
|
|
109
|
-
fontSize: "small"
|
|
110
|
-
})
|
|
111
|
-
}) : undefined
|
|
112
|
-
})
|
|
113
|
+
renderOption: (props, option)=>{
|
|
114
|
+
return /*#__PURE__*/ _createElement("li", {
|
|
115
|
+
...props,
|
|
116
|
+
key: option.value
|
|
117
|
+
}, /*#__PURE__*/ _jsxs(Stack, {
|
|
118
|
+
direction: "row",
|
|
119
|
+
alignItems: "center",
|
|
120
|
+
justifyContent: "space-between",
|
|
121
|
+
width: "100%",
|
|
122
|
+
children: [
|
|
123
|
+
/*#__PURE__*/ _jsx(ListItemText, {
|
|
124
|
+
children: /*#__PURE__*/ _jsx(DatasourceName, {
|
|
125
|
+
name: option.name,
|
|
126
|
+
overridden: option.overridden,
|
|
127
|
+
overriding: option.overriding
|
|
128
|
+
})
|
|
129
|
+
}),
|
|
130
|
+
!option.saved && /*#__PURE__*/ _jsx(ListItemText, {
|
|
131
|
+
children: "Save the dashboard to enable this datasource"
|
|
132
|
+
}),
|
|
133
|
+
/*#__PURE__*/ _jsx(ListItemText, {
|
|
134
|
+
style: {
|
|
135
|
+
textAlign: 'right'
|
|
136
|
+
},
|
|
137
|
+
children: option.groupLabel && option.groupLabel.length > 0 && /*#__PURE__*/ _jsx(Chip, {
|
|
138
|
+
disabled: false,
|
|
139
|
+
label: option.groupLabel,
|
|
140
|
+
size: "small",
|
|
141
|
+
onDelete: option.groupEditLink ? fakeActionEvent : undefined,
|
|
142
|
+
deleteIcon: option.groupEditLink ? /*#__PURE__*/ _jsx(IconButton, {
|
|
143
|
+
href: option.groupEditLink,
|
|
144
|
+
target: "_blank",
|
|
145
|
+
children: /*#__PURE__*/ _jsx(OpenInNewIcon, {
|
|
146
|
+
fontSize: "small"
|
|
113
147
|
})
|
|
114
|
-
|
|
148
|
+
}) : undefined
|
|
115
149
|
})
|
|
116
|
-
}
|
|
117
|
-
|
|
150
|
+
})
|
|
151
|
+
]
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
118
154
|
});
|
|
119
155
|
}
|
|
120
156
|
export function DatasourceName(props) {
|
|
@@ -139,6 +175,9 @@ const OPTION_VALUE_DELIMITER = '_____';
|
|
|
139
175
|
* returns a string value like `{kind}_____{group}_____{name}` that can be used as a Select input value.
|
|
140
176
|
* @param selector
|
|
141
177
|
*/ function selectorToOptionValue(selector) {
|
|
178
|
+
if (isVariableDatasource(selector)) {
|
|
179
|
+
return `${DATASOURCE_VARIABLE_VALUE_PREFIX}${selector}`;
|
|
180
|
+
}
|
|
142
181
|
return [
|
|
143
182
|
selector.kind,
|
|
144
183
|
selector.group ?? '',
|
|
@@ -150,6 +189,9 @@ const OPTION_VALUE_DELIMITER = '_____';
|
|
|
150
189
|
* returns a DatasourceSelector to be used by the query data model.
|
|
151
190
|
* @param optionValue
|
|
152
191
|
*/ function optionValueToSelector(optionValue) {
|
|
192
|
+
if (optionValue.startsWith(DATASOURCE_VARIABLE_VALUE_PREFIX)) {
|
|
193
|
+
return optionValue.split(DATASOURCE_VARIABLE_VALUE_PREFIX)[1];
|
|
194
|
+
}
|
|
153
195
|
const words = optionValue.split(OPTION_VALUE_DELIMITER);
|
|
154
196
|
const kind = words[0];
|
|
155
197
|
const name = words[2];
|
|
@@ -161,5 +203,39 @@ const OPTION_VALUE_DELIMITER = '_____';
|
|
|
161
203
|
name: name === '' ? undefined : name
|
|
162
204
|
};
|
|
163
205
|
}
|
|
206
|
+
export function isVariableDatasource(value) {
|
|
207
|
+
return typeof value === 'string' && value.startsWith(VARIABLE_IDENTIFIER);
|
|
208
|
+
}
|
|
209
|
+
export const datasourceSelectValueToSelector = (value, variables, datasourceSelectItemGroups)=>{
|
|
210
|
+
if (!isVariableDatasource(value)) {
|
|
211
|
+
return value;
|
|
212
|
+
}
|
|
213
|
+
const [variableName] = parseVariables(value);
|
|
214
|
+
const variable = variables[variableName ?? ''];
|
|
215
|
+
// If the variable is not defined or if its value is an array, we cannot determine a selector and return undefined
|
|
216
|
+
if (!variable || Array.isArray(variable.value)) {
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
const associatedDatasource = (datasourceSelectItemGroups || []).flatMap((itemGroup)=>itemGroup.items).find((datasource)=>datasource.name === variable.value);
|
|
220
|
+
// If the variable value is not a datasource, we cannot determine a selector and return undefined
|
|
221
|
+
if (associatedDatasource === undefined) {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
const datasourceSelector = {
|
|
225
|
+
kind: associatedDatasource.selector.kind,
|
|
226
|
+
name: associatedDatasource.selector.name
|
|
227
|
+
};
|
|
228
|
+
return datasourceSelector;
|
|
229
|
+
};
|
|
230
|
+
export const useDatasourceSelectValueToSelector = (value, datasourcePluginKind)=>{
|
|
231
|
+
const { data } = useListDatasourceSelectItems(datasourcePluginKind);
|
|
232
|
+
const variables = useVariableValues();
|
|
233
|
+
if (!isVariableDatasource(value)) {
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
return datasourceSelectValueToSelector(value, variables, data) ?? {
|
|
237
|
+
kind: datasourcePluginKind
|
|
238
|
+
};
|
|
239
|
+
};
|
|
164
240
|
|
|
165
241
|
//# sourceMappingURL=DatasourceSelect.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/DatasourceSelect.tsx"],"sourcesContent":["// Copyright 2023 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport OpenInNewIcon from 'mdi-material-ui/OpenInNew';\nimport {\n Select,\n SelectProps,\n MenuItem,\n Stack,\n Divider,\n ListItemText,\n Chip,\n IconButton,\n Box,\n OutlinedSelectProps,\n BaseSelectProps,\n} from '@mui/material';\nimport { DatasourceSelector } from '@perses-dev/core';\nimport { ReactElement, useMemo } from 'react';\nimport { DatasourceSelectItemSelector, useListDatasourceSelectItems } from '../runtime';\n\n// Props on MUI Select that we don't want people to pass because we're either redefining them or providing them in\n// this component\ntype OmittedMuiProps = 'children' | 'value' | 'onChange';\n\nexport interface DatasourceSelectProps extends Omit<OutlinedSelectProps & BaseSelectProps<string>, OmittedMuiProps> {\n value: DatasourceSelector;\n onChange: (next: DatasourceSelector) => void;\n datasourcePluginKind: string;\n project?: string;\n}\n\n/**\n * Displays a MUI input for selecting a Datasource of a particular kind. Note: The 'value' and `onChange` handler for\n * the input deal with a `DatasourceSelector`.\n */\nexport function DatasourceSelect(props: DatasourceSelectProps): ReactElement {\n const { datasourcePluginKind, value, project, onChange, ...others } = props;\n const { data, isLoading } = useListDatasourceSelectItems(datasourcePluginKind, project);\n // Rebuild the group of the value if not provided\n const defaultValue = useMemo(() => {\n const group = (data ?? [])\n .flatMap((itemGroup) => itemGroup.items)\n .find((item) => {\n return value.kind === item.selector.kind && value.name === item.selector.name && !item.overridden;\n })?.selector.group;\n return { ...value, group };\n }, [value, data]);\n\n // Convert the datasource list into menu items with name/group?/value strings that the Select input can work with\n const menuItems = useMemo(() => {\n return (data ?? []).map((itemGroup) => ({\n group: itemGroup.group,\n editLink: itemGroup.editLink,\n items: itemGroup.items.map((item) => ({\n name: item.name,\n overriding: item.overriding,\n overridden: item.overridden,\n saved: item.saved ?? true,\n group: item.selector.group,\n value: selectorToOptionValue(item.selector),\n })),\n }));\n }, [data]);\n\n // While loading available values, just use an empty string so MUI select doesn't warn about values out of range\n const optionValue = isLoading ? '' : selectorToOptionValue(defaultValue);\n\n // When the user makes a selection, convert the string option value back to a DatasourceSelector\n const handleChange: SelectProps<string>['onChange'] = (e) => {\n const next = optionValueToSelector(e.target.value);\n onChange(next);\n };\n\n // We use a fake action event when we click on the action of the chip (hijack the \"delete\" feature).\n // This is because the href link action is on the `deleteIcon` property already, but the `onDelete` property\n // controls its visibility.\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n const fakeActionEvent = (): void => {};\n\n // TODO:\n // - Does this need a loading indicator of some kind?\n // - The group's edit link is not clickable once selected.\n // - The group's edit link is disabled if datasource is overridden.\n // Ref: https://github.com/mui/material-ui/issues/36572\n return (\n <Select {...others} value={optionValue} onChange={handleChange}>\n {menuItems.map((menuItemGroup) => [\n <Divider key={`${menuItemGroup.group}-divider`} />,\n ...menuItemGroup.items.map((menuItem) => (\n <MenuItem key={menuItem.value} value={menuItem.value} disabled={menuItem.overridden || !menuItem.saved}>\n <Stack direction=\"row\" alignItems=\"center\" justifyContent=\"space-between\" width=\"100%\">\n <ListItemText>\n <DatasourceName\n name={menuItem.name}\n overridden={menuItem.overridden}\n overriding={menuItem.overriding}\n />\n </ListItemText>\n {!menuItem.saved && <ListItemText>Save the dashboard to enable this datasource</ListItemText>}\n <ListItemText style={{ textAlign: 'right' }}>\n {menuItemGroup.group && menuItemGroup.group.length > 0 && (\n <Chip\n disabled={false}\n label={menuItemGroup.group}\n size=\"small\"\n onDelete={menuItemGroup.editLink ? fakeActionEvent : undefined}\n deleteIcon={\n menuItemGroup.editLink ? (\n <IconButton href={menuItemGroup.editLink} target=\"_blank\">\n <OpenInNewIcon fontSize=\"small\" />\n </IconButton>\n ) : undefined\n }\n />\n )}\n </ListItemText>\n </Stack>\n </MenuItem>\n )),\n ])}\n </Select>\n );\n}\n\nexport function DatasourceName(props: { name: string; overridden?: boolean; overriding?: boolean }): ReactElement {\n const { name, overridden, overriding } = props;\n return (\n <>\n {`${name} `}\n {!overridden && overriding && (\n <Box display=\"inline\" fontWeight=\"normal\" color={(theme) => theme.palette.primary.main}>\n (overriding)\n </Box>\n )}\n {overridden && '(overridden)'}\n </>\n );\n}\n\n// Delimiter used to stringify/parse option values\nconst OPTION_VALUE_DELIMITER = '_____';\n\n/**\n * Given a DatasourceSelectItemSelector,\n * returns a string value like `{kind}_____{group}_____{name}` that can be used as a Select input value.\n * @param selector\n */\nfunction selectorToOptionValue(selector: DatasourceSelectItemSelector): string {\n return [selector.kind, selector.group ?? '', selector.name ?? ''].join(OPTION_VALUE_DELIMITER);\n}\n\n/**\n * Given an option value name like `{kind}_____{group}_____{name}`,\n * returns a DatasourceSelector to be used by the query data model.\n * @param optionValue\n */\nfunction optionValueToSelector(optionValue: string): DatasourceSelector {\n const words = optionValue.split(OPTION_VALUE_DELIMITER);\n const kind = words[0];\n const name = words[2];\n if (kind === undefined || name === undefined) {\n throw new Error('Invalid optionValue string');\n }\n return {\n kind,\n name: name === '' ? undefined : name,\n };\n}\n"],"names":["OpenInNewIcon","Select","MenuItem","Stack","Divider","ListItemText","Chip","IconButton","Box","useMemo","useListDatasourceSelectItems","DatasourceSelect","props","datasourcePluginKind","value","project","onChange","others","data","isLoading","defaultValue","group","flatMap","itemGroup","items","find","item","kind","selector","name","overridden","menuItems","map","editLink","overriding","saved","selectorToOptionValue","optionValue","handleChange","e","next","optionValueToSelector","target","fakeActionEvent","menuItemGroup","menuItem","disabled","direction","alignItems","justifyContent","width","DatasourceName","style","textAlign","length","label","size","onDelete","undefined","deleteIcon","href","fontSize","display","fontWeight","color","theme","palette","primary","main","OPTION_VALUE_DELIMITER","join","words","split","Error"],"mappings":"AAAA,oCAAoC;AACpC,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,6CAA6C;AAC7C,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;;AAEjC,OAAOA,mBAAmB,4BAA4B;AACtD,SACEC,MAAM,EAENC,QAAQ,EACRC,KAAK,EACLC,OAAO,EACPC,YAAY,EACZC,IAAI,EACJC,UAAU,EACVC,GAAG,QAGE,gBAAgB;AAEvB,SAAuBC,OAAO,QAAQ,QAAQ;AAC9C,SAAuCC,4BAA4B,QAAQ,aAAa;AAaxF;;;CAGC,GACD,OAAO,SAASC,iBAAiBC,KAA4B;IAC3D,MAAM,EAAEC,oBAAoB,EAAEC,KAAK,EAAEC,OAAO,EAAEC,QAAQ,EAAE,GAAGC,QAAQ,GAAGL;IACtE,MAAM,EAAEM,IAAI,EAAEC,SAAS,EAAE,GAAGT,6BAA6BG,sBAAsBE;IAC/E,iDAAiD;IACjD,MAAMK,eAAeX,QAAQ;QAC3B,MAAMY,QAAQ,AAACH,CAAAA,QAAQ,EAAE,AAAD,EACrBI,OAAO,CAAC,CAACC,YAAcA,UAAUC,KAAK,EACtCC,IAAI,CAAC,CAACC;YACL,OAAOZ,MAAMa,IAAI,KAAKD,KAAKE,QAAQ,CAACD,IAAI,IAAIb,MAAMe,IAAI,KAAKH,KAAKE,QAAQ,CAACC,IAAI,IAAI,CAACH,KAAKI,UAAU;QACnG,IAAIF,SAASP;QACf,OAAO;YAAE,GAAGP,KAAK;YAAEO;QAAM;IAC3B,GAAG;QAACP;QAAOI;KAAK;IAEhB,iHAAiH;IACjH,MAAMa,YAAYtB,QAAQ;QACxB,OAAO,AAACS,CAAAA,QAAQ,EAAE,AAAD,EAAGc,GAAG,CAAC,CAACT,YAAe,CAAA;gBACtCF,OAAOE,UAAUF,KAAK;gBACtBY,UAAUV,UAAUU,QAAQ;gBAC5BT,OAAOD,UAAUC,KAAK,CAACQ,GAAG,CAAC,CAACN,OAAU,CAAA;wBACpCG,MAAMH,KAAKG,IAAI;wBACfK,YAAYR,KAAKQ,UAAU;wBAC3BJ,YAAYJ,KAAKI,UAAU;wBAC3BK,OAAOT,KAAKS,KAAK,IAAI;wBACrBd,OAAOK,KAAKE,QAAQ,CAACP,KAAK;wBAC1BP,OAAOsB,sBAAsBV,KAAKE,QAAQ;oBAC5C,CAAA;YACF,CAAA;IACF,GAAG;QAACV;KAAK;IAET,gHAAgH;IAChH,MAAMmB,cAAclB,YAAY,KAAKiB,sBAAsBhB;IAE3D,gGAAgG;IAChG,MAAMkB,eAAgD,CAACC;QACrD,MAAMC,OAAOC,sBAAsBF,EAAEG,MAAM,CAAC5B,KAAK;QACjDE,SAASwB;IACX;IAEA,oGAAoG;IACpG,4GAA4G;IAC5G,2BAA2B;IAC3B,gEAAgE;IAChE,MAAMG,kBAAkB,KAAa;IAErC,QAAQ;IACR,sDAAsD;IACtD,2DAA2D;IAC3D,oEAAoE;IACpE,0DAA0D;IAC1D,qBACE,KAAC1C;QAAQ,GAAGgB,MAAM;QAAEH,OAAOuB;QAAarB,UAAUsB;kBAC/CP,UAAUC,GAAG,CAAC,CAACY,gBAAkB;8BAChC,KAACxC,aAAa,GAAGwC,cAAcvB,KAAK,CAAC,QAAQ,CAAC;mBAC3CuB,cAAcpB,KAAK,CAACQ,GAAG,CAAC,CAACa,yBAC1B,KAAC3C;wBAA8BY,OAAO+B,SAAS/B,KAAK;wBAAEgC,UAAUD,SAASf,UAAU,IAAI,CAACe,SAASV,KAAK;kCACpG,cAAA,MAAChC;4BAAM4C,WAAU;4BAAMC,YAAW;4BAASC,gBAAe;4BAAgBC,OAAM;;8CAC9E,KAAC7C;8CACC,cAAA,KAAC8C;wCACCtB,MAAMgB,SAAShB,IAAI;wCACnBC,YAAYe,SAASf,UAAU;wCAC/BI,YAAYW,SAASX,UAAU;;;gCAGlC,CAACW,SAASV,KAAK,kBAAI,KAAC9B;8CAAa;;8CAClC,KAACA;oCAAa+C,OAAO;wCAAEC,WAAW;oCAAQ;8CACvCT,cAAcvB,KAAK,IAAIuB,cAAcvB,KAAK,CAACiC,MAAM,GAAG,mBACnD,KAAChD;wCACCwC,UAAU;wCACVS,OAAOX,cAAcvB,KAAK;wCAC1BmC,MAAK;wCACLC,UAAUb,cAAcX,QAAQ,GAAGU,kBAAkBe;wCACrDC,YACEf,cAAcX,QAAQ,iBACpB,KAAC1B;4CAAWqD,MAAMhB,cAAcX,QAAQ;4CAAES,QAAO;sDAC/C,cAAA,KAAC1C;gDAAc6D,UAAS;;6CAExBH;;;;;uBAtBDb,SAAS/B,KAAK;aA8BhC;;AAGP;AAEA,OAAO,SAASqC,eAAevC,KAAmE;IAChG,MAAM,EAAEiB,IAAI,EAAEC,UAAU,EAAEI,UAAU,EAAE,GAAGtB;IACzC,qBACE;;YACG,GAAGiB,KAAK,CAAC,CAAC;YACV,CAACC,cAAcI,4BACd,KAAC1B;gBAAIsD,SAAQ;gBAASC,YAAW;gBAASC,OAAO,CAACC,QAAUA,MAAMC,OAAO,CAACC,OAAO,CAACC,IAAI;0BAAE;;YAIzFtC,cAAc;;;AAGrB;AAEA,kDAAkD;AAClD,MAAMuC,yBAAyB;AAE/B;;;;CAIC,GACD,SAASjC,sBAAsBR,QAAsC;IACnE,OAAO;QAACA,SAASD,IAAI;QAAEC,SAASP,KAAK,IAAI;QAAIO,SAASC,IAAI,IAAI;KAAG,CAACyC,IAAI,CAACD;AACzE;AAEA;;;;CAIC,GACD,SAAS5B,sBAAsBJ,WAAmB;IAChD,MAAMkC,QAAQlC,YAAYmC,KAAK,CAACH;IAChC,MAAM1C,OAAO4C,KAAK,CAAC,EAAE;IACrB,MAAM1C,OAAO0C,KAAK,CAAC,EAAE;IACrB,IAAI5C,SAAS+B,aAAa7B,SAAS6B,WAAW;QAC5C,MAAM,IAAIe,MAAM;IAClB;IACA,OAAO;QACL9C;QACAE,MAAMA,SAAS,KAAK6B,YAAY7B;IAClC;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../src/components/DatasourceSelect.tsx"],"sourcesContent":["// Copyright 2023 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport OpenInNewIcon from 'mdi-material-ui/OpenInNew';\nimport {\n Stack,\n ListItemText,\n Chip,\n IconButton,\n Box,\n OutlinedSelectProps,\n BaseSelectProps,\n Autocomplete,\n TextField,\n} from '@mui/material';\nimport { DatasourceSelector, VariableName } from '@perses-dev/core';\nimport { ReactElement, useMemo } from 'react';\nimport {\n DatasourceSelectItem,\n DatasourceSelectItemGroup,\n DatasourceSelectItemSelector,\n useListDatasourceSelectItems,\n useVariableValues,\n VariableStateMap,\n} from '../runtime';\nimport { parseVariables } from '../utils';\n\nconst DATASOURCE_VARIABLE_VALUE_PREFIX = '__DATASOURCE_VARIABLE_VALUE__';\nconst VARIABLE_IDENTIFIER = '$';\n// Props on MUI Select that we don't want people to pass because we're either redefining them or providing them in\n// this component\ntype OmittedMuiProps = 'children' | 'value' | 'onChange';\n\ntype DataSourceOption = {\n groupEditLink?: string;\n groupLabel?: string;\n value: string;\n} & Omit<DatasourceSelectItem, 'selector'> &\n Omit<DatasourceSelectItem['selector'], 'kind'>;\n\nconst emptyDatasourceOption: DataSourceOption = { name: '', value: '' };\n\nexport type DatasourceSelectValue<T = DatasourceSelector> = T | VariableName;\n\nexport interface DatasourceSelectProps extends Omit<OutlinedSelectProps & BaseSelectProps<string>, OmittedMuiProps> {\n value: DatasourceSelectValue;\n onChange: (next: DatasourceSelectValue) => void;\n datasourcePluginKind: string;\n project?: string;\n}\n\n/**\n * Displays a MUI input for selecting a Datasource of a particular kind. Note: The 'value' and `onChange` handler for\n * the input deal with a `DatasourceSelector`.\n */\nexport function DatasourceSelect(props: DatasourceSelectProps): ReactElement {\n const { datasourcePluginKind, value, project, onChange, ...others } = props;\n const { data, isLoading } = useListDatasourceSelectItems(datasourcePluginKind, project);\n const variables = useVariableValues();\n\n const defaultValue = useMemo<VariableName | DatasourceSelectItemSelector>(() => {\n if (isVariableDatasource(value)) {\n return value;\n }\n\n const group = (data ?? [])\n .flatMap((itemGroup) => itemGroup.items)\n .find((item) => {\n return value.kind === item.selector.kind && value.name === item.selector.name && !item.overridden;\n })?.selector.group;\n return { ...value, group };\n }, [value, data]);\n\n const options = useMemo<DataSourceOption[]>(() => {\n const datasourceOptions = (data || []).flatMap<DataSourceOption>((itemGroup) =>\n itemGroup.items.map<DataSourceOption>((item) => ({\n groupLabel: itemGroup.group,\n groupEditLink: itemGroup.editLink,\n name: item.name,\n overriding: item.overriding,\n overridden: item.overridden,\n saved: item.saved ?? true,\n group: item.selector.group,\n value: selectorToOptionValue(item.selector),\n }))\n );\n\n const datasourceOptionsMap = new Map(datasourceOptions.map((option) => [option.name, option]));\n\n const variableOptions = Object.entries(variables).flatMap<DataSourceOption>(([name, variable]) => {\n if (Array.isArray(variable.value)) return [];\n\n const associatedDatasource = datasourceOptionsMap.get(variable.value ?? '');\n if (!associatedDatasource) return [];\n\n return {\n groupLabel: 'Variables',\n name: `${VARIABLE_IDENTIFIER}${name}`,\n saved: true,\n value: `${DATASOURCE_VARIABLE_VALUE_PREFIX}${VARIABLE_IDENTIFIER}${name}`,\n };\n });\n\n return [...datasourceOptions, ...variableOptions];\n }, [data, variables]);\n\n // While loading available values, just use an empty datasource option so MUI select doesn't warn about values out of range\n const optionValue = isLoading\n ? emptyDatasourceOption\n : options.find((option) => option.value === selectorToOptionValue(defaultValue));\n\n // When the user makes a selection, convert the string option value back to a DatasourceSelector\n const handleChange = (selectedOption: DataSourceOption | null): void => {\n if (selectedOption) {\n const next = optionValueToSelector(selectedOption?.value || '');\n onChange(next);\n } else {\n onChange({ kind: datasourcePluginKind });\n }\n };\n\n // We use a fake action event when we click on the action of the chip (hijack the \"delete\" feature).\n // This is because the href link action is on the `deleteIcon` property already, but the `onDelete` property\n // controls its visibility.\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n const fakeActionEvent = (): void => {};\n\n return (\n <Autocomplete<DataSourceOption>\n options={options}\n renderInput={(params) => <TextField {...params} label={others.label} placeholder=\"\" />}\n groupBy={(option) => option.groupLabel || 'No group'}\n getOptionLabel={(option) => {\n return option.name;\n }}\n onChange={(_, v) => handleChange(v)}\n value={optionValue}\n renderOption={(props, option) => {\n return (\n <li {...props} key={option.value}>\n <Stack direction=\"row\" alignItems=\"center\" justifyContent=\"space-between\" width=\"100%\">\n <ListItemText>\n <DatasourceName name={option.name} overridden={option.overridden} overriding={option.overriding} />\n </ListItemText>\n {!option.saved && <ListItemText>Save the dashboard to enable this datasource</ListItemText>}\n <ListItemText style={{ textAlign: 'right' }}>\n {option.groupLabel && option.groupLabel.length > 0 && (\n <Chip\n disabled={false}\n label={option.groupLabel}\n size=\"small\"\n onDelete={option.groupEditLink ? fakeActionEvent : undefined}\n deleteIcon={\n option.groupEditLink ? (\n <IconButton href={option.groupEditLink} target=\"_blank\">\n <OpenInNewIcon fontSize=\"small\" />\n </IconButton>\n ) : undefined\n }\n />\n )}\n </ListItemText>\n </Stack>\n </li>\n );\n }}\n />\n );\n}\n\nexport function DatasourceName(props: { name: string; overridden?: boolean; overriding?: boolean }): ReactElement {\n const { name, overridden, overriding } = props;\n return (\n <>\n {`${name} `}\n {!overridden && overriding && (\n <Box display=\"inline\" fontWeight=\"normal\" color={(theme) => theme.palette.primary.main}>\n (overriding)\n </Box>\n )}\n {overridden && '(overridden)'}\n </>\n );\n}\n\n// Delimiter used to stringify/parse option values\nconst OPTION_VALUE_DELIMITER = '_____';\n\n/**\n * Given a DatasourceSelectItemSelector,\n * returns a string value like `{kind}_____{group}_____{name}` that can be used as a Select input value.\n * @param selector\n */\nfunction selectorToOptionValue(selector: DatasourceSelectItemSelector | VariableName): string {\n if (isVariableDatasource(selector)) {\n return `${DATASOURCE_VARIABLE_VALUE_PREFIX}${selector}`;\n }\n return [selector.kind, selector.group ?? '', selector.name ?? ''].join(OPTION_VALUE_DELIMITER);\n}\n\n/**\n * Given an option value name like `{kind}_____{group}_____{name}`,\n * returns a DatasourceSelector to be used by the query data model.\n * @param optionValue\n */\nfunction optionValueToSelector(optionValue: string): DatasourceSelectValue {\n if (optionValue.startsWith(DATASOURCE_VARIABLE_VALUE_PREFIX)) {\n return optionValue.split(DATASOURCE_VARIABLE_VALUE_PREFIX)[1]!;\n }\n\n const words = optionValue.split(OPTION_VALUE_DELIMITER);\n const kind = words[0];\n const name = words[2];\n if (kind === undefined || name === undefined) {\n throw new Error('Invalid optionValue string');\n }\n return {\n kind,\n name: name === '' ? undefined : name,\n };\n}\n\nexport function isVariableDatasource(value: DatasourceSelectValue | undefined): value is VariableName {\n return typeof value === 'string' && value.startsWith(VARIABLE_IDENTIFIER);\n}\n\nexport const datasourceSelectValueToSelector = (\n value: DatasourceSelectValue | undefined,\n variables: VariableStateMap,\n datasourceSelectItemGroups: DatasourceSelectItemGroup[] | undefined\n): DatasourceSelector | undefined => {\n if (!isVariableDatasource(value)) {\n return value;\n }\n\n const [variableName] = parseVariables(value);\n const variable = variables[variableName ?? ''];\n\n // If the variable is not defined or if its value is an array, we cannot determine a selector and return undefined\n if (!variable || Array.isArray(variable.value)) {\n return undefined;\n }\n\n const associatedDatasource = (datasourceSelectItemGroups || [])\n .flatMap((itemGroup) => itemGroup.items)\n .find((datasource) => datasource.name === variable.value);\n\n // If the variable value is not a datasource, we cannot determine a selector and return undefined\n if (associatedDatasource === undefined) {\n return undefined;\n }\n\n const datasourceSelector: DatasourceSelector = {\n kind: associatedDatasource.selector.kind,\n name: associatedDatasource.selector.name,\n };\n\n return datasourceSelector;\n};\n\nexport const useDatasourceSelectValueToSelector = (\n value: DatasourceSelectValue,\n datasourcePluginKind: string\n): DatasourceSelector => {\n const { data } = useListDatasourceSelectItems(datasourcePluginKind);\n const variables = useVariableValues();\n if (!isVariableDatasource(value)) {\n return value;\n }\n\n return datasourceSelectValueToSelector(value, variables, data) ?? { kind: datasourcePluginKind };\n};\n"],"names":["OpenInNewIcon","Stack","ListItemText","Chip","IconButton","Box","Autocomplete","TextField","useMemo","useListDatasourceSelectItems","useVariableValues","parseVariables","DATASOURCE_VARIABLE_VALUE_PREFIX","VARIABLE_IDENTIFIER","emptyDatasourceOption","name","value","DatasourceSelect","props","datasourcePluginKind","project","onChange","others","data","isLoading","variables","defaultValue","isVariableDatasource","group","flatMap","itemGroup","items","find","item","kind","selector","overridden","options","datasourceOptions","map","groupLabel","groupEditLink","editLink","overriding","saved","selectorToOptionValue","datasourceOptionsMap","Map","option","variableOptions","Object","entries","variable","Array","isArray","associatedDatasource","get","optionValue","handleChange","selectedOption","next","optionValueToSelector","fakeActionEvent","renderInput","params","label","placeholder","groupBy","getOptionLabel","_","v","renderOption","li","key","direction","alignItems","justifyContent","width","DatasourceName","style","textAlign","length","disabled","size","onDelete","undefined","deleteIcon","href","target","fontSize","display","fontWeight","color","theme","palette","primary","main","OPTION_VALUE_DELIMITER","join","startsWith","split","words","Error","datasourceSelectValueToSelector","datasourceSelectItemGroups","variableName","datasource","datasourceSelector","useDatasourceSelectValueToSelector"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/PluginRegistry/PluginRegistry.tsx"],"sourcesContent":["// Copyright 2023 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { UnknownSpec, useEvent } from '@perses-dev/core';\nimport { useRef, useCallback, useMemo, ReactNode, ReactElement } from 'react';\nimport {\n PluginModuleResource,\n PluginType,\n PluginImplementation,\n Plugin,\n PluginLoader,\n DefaultPluginKinds,\n} from '../../model';\nimport { PluginRegistryContext } from '../../runtime';\nimport { usePluginIndexes, getTypeAndKindKey } from './plugin-indexes';\n\nexport interface PluginRegistryProps {\n pluginLoader: PluginLoader;\n defaultPluginKinds?: DefaultPluginKinds;\n children?: ReactNode;\n}\n\n/**\n * PluginRegistryContext provider that keeps track of all available plugins and provides an API for getting them or\n * querying the metadata about them.\n */\nexport function PluginRegistry(props: PluginRegistryProps): ReactElement {\n const {\n pluginLoader: { getInstalledPlugins, importPluginModule },\n children,\n defaultPluginKinds,\n } = props;\n\n const getPluginIndexes = usePluginIndexes(getInstalledPlugins);\n\n // De-dupe calls to import plugin modules\n const importCache = useRef(new Map<PluginModuleResource, Promise<unknown>>());\n\n // Do useEvent here since this accesses the importPluginModule prop and we want a stable reference to it for the\n // callback below\n const loadPluginModule = useEvent((resource: PluginModuleResource) => {\n let request = importCache.current.get(resource);\n if (request === undefined) {\n request = importPluginModule(resource);\n importCache.current.set(resource, request);\n\n // Remove failed requests from the cache so they can potentially be retried\n request.catch(() => importCache.current.delete(resource));\n }\n return request;\n });\n\n const getPlugin = useCallback(\n async <T extends PluginType>(kind: T, name: string): Promise<PluginImplementation<T>> => {\n // Get the indexes of the installed plugins\n const pluginIndexes = await getPluginIndexes();\n\n // Figure out what module the plugin is in by looking in the index\n const typeAndKindKey = getTypeAndKindKey(kind, name);\n const resource = pluginIndexes.pluginResourcesByNameAndKind.get(typeAndKindKey);\n if (resource === undefined) {\n throw new Error(`A ${name} plugin for kind '${kind}' is not installed`);\n }\n\n // Treat the plugin module as a bunch of named exports that have plugins\n const pluginModule = (await loadPluginModule(resource)) as Record<string, Plugin<UnknownSpec>>;\n\n // We currently assume that plugin modules will have named exports that match the kinds they handle\n const plugin = pluginModule[name];\n if (plugin === undefined) {\n throw new Error(\n `The ${name} plugin for kind '${kind}' is missing from the ${resource.metadata.name} plugin module`\n );\n }\n\n return plugin as PluginImplementation<T>;\n },\n [getPluginIndexes, loadPluginModule]\n );\n\n const listPluginMetadata = useCallback(\n async (pluginTypes:
|
|
1
|
+
{"version":3,"sources":["../../../src/components/PluginRegistry/PluginRegistry.tsx"],"sourcesContent":["// Copyright 2023 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { UnknownSpec, useEvent } from '@perses-dev/core';\nimport { useRef, useCallback, useMemo, ReactNode, ReactElement } from 'react';\nimport {\n PluginModuleResource,\n PluginType,\n PluginImplementation,\n Plugin,\n PluginLoader,\n DefaultPluginKinds,\n} from '../../model';\nimport { PluginRegistryContext } from '../../runtime';\nimport { usePluginIndexes, getTypeAndKindKey } from './plugin-indexes';\n\nexport interface PluginRegistryProps {\n pluginLoader: PluginLoader;\n defaultPluginKinds?: DefaultPluginKinds;\n children?: ReactNode;\n}\n\n/**\n * PluginRegistryContext provider that keeps track of all available plugins and provides an API for getting them or\n * querying the metadata about them.\n */\nexport function PluginRegistry(props: PluginRegistryProps): ReactElement {\n const {\n pluginLoader: { getInstalledPlugins, importPluginModule },\n children,\n defaultPluginKinds,\n } = props;\n\n const getPluginIndexes = usePluginIndexes(getInstalledPlugins);\n\n // De-dupe calls to import plugin modules\n const importCache = useRef(new Map<PluginModuleResource, Promise<unknown>>());\n\n // Do useEvent here since this accesses the importPluginModule prop and we want a stable reference to it for the\n // callback below\n const loadPluginModule = useEvent((resource: PluginModuleResource) => {\n let request = importCache.current.get(resource);\n if (request === undefined) {\n request = importPluginModule(resource);\n importCache.current.set(resource, request);\n\n // Remove failed requests from the cache so they can potentially be retried\n request.catch(() => importCache.current.delete(resource));\n }\n return request;\n });\n\n const getPlugin = useCallback(\n async <T extends PluginType>(kind: T, name: string): Promise<PluginImplementation<T>> => {\n // Get the indexes of the installed plugins\n const pluginIndexes = await getPluginIndexes();\n\n // Figure out what module the plugin is in by looking in the index\n const typeAndKindKey = getTypeAndKindKey(kind, name);\n const resource = pluginIndexes.pluginResourcesByNameAndKind.get(typeAndKindKey);\n if (resource === undefined) {\n throw new Error(`A ${name} plugin for kind '${kind}' is not installed`);\n }\n\n // Treat the plugin module as a bunch of named exports that have plugins\n const pluginModule = (await loadPluginModule(resource)) as Record<string, Plugin<UnknownSpec>>;\n\n // We currently assume that plugin modules will have named exports that match the kinds they handle\n const plugin = pluginModule[name];\n if (plugin === undefined) {\n throw new Error(\n `The ${name} plugin for kind '${kind}' is missing from the ${resource.metadata.name} plugin module`\n );\n }\n\n return plugin as PluginImplementation<T>;\n },\n [getPluginIndexes, loadPluginModule]\n );\n\n const listPluginMetadata = useCallback(\n async (pluginTypes: PluginType[]) => {\n const pluginIndexes = await getPluginIndexes();\n return pluginTypes.flatMap((type) => pluginIndexes.pluginMetadataByKind.get(type) ?? []);\n },\n [getPluginIndexes]\n );\n\n // Create the registry's context value and render\n const context = useMemo(\n () => ({ getPlugin, listPluginMetadata, defaultPluginKinds }),\n [getPlugin, listPluginMetadata, defaultPluginKinds]\n );\n return <PluginRegistryContext.Provider value={context}>{children}</PluginRegistryContext.Provider>;\n}\n"],"names":["useEvent","useRef","useCallback","useMemo","PluginRegistryContext","usePluginIndexes","getTypeAndKindKey","PluginRegistry","props","pluginLoader","getInstalledPlugins","importPluginModule","children","defaultPluginKinds","getPluginIndexes","importCache","Map","loadPluginModule","resource","request","current","get","undefined","set","catch","delete","getPlugin","kind","name","pluginIndexes","typeAndKindKey","pluginResourcesByNameAndKind","Error","pluginModule","plugin","metadata","listPluginMetadata","pluginTypes","flatMap","type","pluginMetadataByKind","context","Provider","value"],"mappings":"AAAA,oCAAoC;AACpC,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,6CAA6C;AAC7C,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;;AAEjC,SAAsBA,QAAQ,QAAQ,mBAAmB;AACzD,SAASC,MAAM,EAAEC,WAAW,EAAEC,OAAO,QAAiC,QAAQ;AAS9E,SAASC,qBAAqB,QAAQ,gBAAgB;AACtD,SAASC,gBAAgB,EAAEC,iBAAiB,QAAQ,mBAAmB;AAQvE;;;CAGC,GACD,OAAO,SAASC,eAAeC,KAA0B;IACvD,MAAM,EACJC,cAAc,EAAEC,mBAAmB,EAAEC,kBAAkB,EAAE,EACzDC,QAAQ,EACRC,kBAAkB,EACnB,GAAGL;IAEJ,MAAMM,mBAAmBT,iBAAiBK;IAE1C,yCAAyC;IACzC,MAAMK,cAAcd,OAAO,IAAIe;IAE/B,gHAAgH;IAChH,iBAAiB;IACjB,MAAMC,mBAAmBjB,SAAS,CAACkB;QACjC,IAAIC,UAAUJ,YAAYK,OAAO,CAACC,GAAG,CAACH;QACtC,IAAIC,YAAYG,WAAW;YACzBH,UAAUR,mBAAmBO;YAC7BH,YAAYK,OAAO,CAACG,GAAG,CAACL,UAAUC;YAElC,2EAA2E;YAC3EA,QAAQK,KAAK,CAAC,IAAMT,YAAYK,OAAO,CAACK,MAAM,CAACP;QACjD;QACA,OAAOC;IACT;IAEA,MAAMO,YAAYxB,YAChB,OAA6ByB,MAASC;QACpC,2CAA2C;QAC3C,MAAMC,gBAAgB,MAAMf;QAE5B,kEAAkE;QAClE,MAAMgB,iBAAiBxB,kBAAkBqB,MAAMC;QAC/C,MAAMV,WAAWW,cAAcE,4BAA4B,CAACV,GAAG,CAACS;QAChE,IAAIZ,aAAaI,WAAW;YAC1B,MAAM,IAAIU,MAAM,CAAC,EAAE,EAAEJ,KAAK,kBAAkB,EAAED,KAAK,kBAAkB,CAAC;QACxE;QAEA,wEAAwE;QACxE,MAAMM,eAAgB,MAAMhB,iBAAiBC;QAE7C,mGAAmG;QACnG,MAAMgB,SAASD,YAAY,CAACL,KAAK;QACjC,IAAIM,WAAWZ,WAAW;YACxB,MAAM,IAAIU,MACR,CAAC,IAAI,EAAEJ,KAAK,kBAAkB,EAAED,KAAK,sBAAsB,EAAET,SAASiB,QAAQ,CAACP,IAAI,CAAC,cAAc,CAAC;QAEvG;QAEA,OAAOM;IACT,GACA;QAACpB;QAAkBG;KAAiB;IAGtC,MAAMmB,qBAAqBlC,YACzB,OAAOmC;QACL,MAAMR,gBAAgB,MAAMf;QAC5B,OAAOuB,YAAYC,OAAO,CAAC,CAACC,OAASV,cAAcW,oBAAoB,CAACnB,GAAG,CAACkB,SAAS,EAAE;IACzF,GACA;QAACzB;KAAiB;IAGpB,iDAAiD;IACjD,MAAM2B,UAAUtC,QACd,IAAO,CAAA;YAAEuB;YAAWU;YAAoBvB;QAAmB,CAAA,GAC3D;QAACa;QAAWU;QAAoBvB;KAAmB;IAErD,qBAAO,KAACT,sBAAsBsC,QAAQ;QAACC,OAAOF;kBAAU7B;;AAC1D"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PluginLoader, PluginMetadataWithModule, PluginModuleResource, PluginType } from '../../model';
|
|
2
2
|
export interface PluginIndexes {
|
|
3
3
|
pluginResourcesByNameAndKind: Map<string, PluginModuleResource>;
|
|
4
|
-
pluginMetadataByKind: Map<
|
|
4
|
+
pluginMetadataByKind: Map<PluginType, PluginMetadataWithModule[]>;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* Returns an async callback for getting indexes of the installed plugin data.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-indexes.d.ts","sourceRoot":"","sources":["../../../src/components/PluginRegistry/plugin-indexes.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,YAAY,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEvG,MAAM,WAAW,aAAa;IAE5B,4BAA4B,EAAE,GAAG,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAEhE,oBAAoB,EAAE,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"plugin-indexes.d.ts","sourceRoot":"","sources":["../../../src/components/PluginRegistry/plugin-indexes.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,YAAY,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEvG,MAAM,WAAW,aAAa;IAE5B,4BAA4B,EAAE,GAAG,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAEhE,oBAAoB,EAAE,GAAG,CAAC,UAAU,EAAE,wBAAwB,EAAE,CAAC,CAAC;CACnE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,mBAAmB,EAAE,YAAY,CAAC,qBAAqB,CAAC,GACvD,MAAM,OAAO,CAAC,aAAa,CAAC,CAuD9B;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAExE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/PluginRegistry/plugin-indexes.ts"],"sourcesContent":["// Copyright 2023 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { useEvent } from '@perses-dev/core';\nimport { useCallback, useRef } from 'react';\nimport { PluginLoader, PluginMetadataWithModule, PluginModuleResource, PluginType } from '../../model';\n\nexport interface PluginIndexes {\n // Plugin resources by plugin type and kind (i.e. look up what module a plugin type and kind is in)\n pluginResourcesByNameAndKind: Map<string, PluginModuleResource>;\n // Plugin metadata by plugin type\n pluginMetadataByKind: Map<
|
|
1
|
+
{"version":3,"sources":["../../../src/components/PluginRegistry/plugin-indexes.ts"],"sourcesContent":["// Copyright 2023 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { useEvent } from '@perses-dev/core';\nimport { useCallback, useRef } from 'react';\nimport { PluginLoader, PluginMetadataWithModule, PluginModuleResource, PluginType } from '../../model';\n\nexport interface PluginIndexes {\n // Plugin resources by plugin type and kind (i.e. look up what module a plugin type and kind is in)\n pluginResourcesByNameAndKind: Map<string, PluginModuleResource>;\n // Plugin metadata by plugin type\n pluginMetadataByKind: Map<PluginType, PluginMetadataWithModule[]>;\n}\n\n/**\n * Returns an async callback for getting indexes of the installed plugin data.\n */\nexport function usePluginIndexes(\n getInstalledPlugins: PluginLoader['getInstalledPlugins']\n): () => Promise<PluginIndexes> {\n // Creates indexes from the installed plugins data (does useEvent because this accesses the getInstalledPlugins prop\n // and we want a stable reference for the callback below)\n const createPluginIndexes = useEvent(async (): Promise<PluginIndexes> => {\n const installedPlugins = await getInstalledPlugins();\n\n // Create the two indexes from the installed plugins\n const pluginResourcesByNameAndKind = new Map<string, PluginModuleResource>();\n const pluginMetadataByKind = new Map<PluginType, PluginMetadataWithModule[]>();\n\n for (const resource of installedPlugins) {\n for (const pluginMetadata of resource.spec.plugins) {\n const {\n kind,\n spec: { name },\n } = pluginMetadata;\n\n // Index the plugin by type and kind to point at the module that contains it\n const key = getTypeAndKindKey(kind, name);\n if (pluginResourcesByNameAndKind.has(key)) {\n console.warn(`Got more than one ${kind} plugin for kind ${name}`);\n }\n pluginResourcesByNameAndKind.set(key, resource);\n\n // Index the metadata by plugin type\n let list = pluginMetadataByKind.get(kind);\n if (list === undefined) {\n list = [];\n pluginMetadataByKind.set(kind, list);\n }\n list.push({ ...pluginMetadata, module: resource.metadata });\n }\n }\n\n return {\n pluginResourcesByNameAndKind,\n pluginMetadataByKind,\n };\n });\n\n // De-dupe creating plugin indexes (i.e. requests to get installed plugins)\n const pluginIndexesCache = useRef<Promise<PluginIndexes> | undefined>(undefined);\n const getPluginIndexes = useCallback(() => {\n let request = pluginIndexesCache.current;\n if (request === undefined) {\n request = createPluginIndexes();\n pluginIndexesCache.current = request;\n\n // Remove failed requests from the cache so they can potentially be retried\n request.catch(() => pluginIndexesCache.current === undefined);\n }\n return request;\n }, [createPluginIndexes]);\n\n return getPluginIndexes;\n}\n\n/**\n * Gets a unique key for a plugin type/kind that can be used as a cache key.\n */\nexport function getTypeAndKindKey(kind: PluginType, name: string): string {\n return `${kind}:${name}`;\n}\n"],"names":["useEvent","useCallback","useRef","usePluginIndexes","getInstalledPlugins","createPluginIndexes","installedPlugins","pluginResourcesByNameAndKind","Map","pluginMetadataByKind","resource","pluginMetadata","spec","plugins","kind","name","key","getTypeAndKindKey","has","console","warn","set","list","get","undefined","push","module","metadata","pluginIndexesCache","getPluginIndexes","request","current","catch"],"mappings":"AAAA,oCAAoC;AACpC,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,6CAA6C;AAC7C,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AAEjC,SAASA,QAAQ,QAAQ,mBAAmB;AAC5C,SAASC,WAAW,EAAEC,MAAM,QAAQ,QAAQ;AAU5C;;CAEC,GACD,OAAO,SAASC,iBACdC,mBAAwD;IAExD,oHAAoH;IACpH,yDAAyD;IACzD,MAAMC,sBAAsBL,SAAS;QACnC,MAAMM,mBAAmB,MAAMF;QAE/B,oDAAoD;QACpD,MAAMG,+BAA+B,IAAIC;QACzC,MAAMC,uBAAuB,IAAID;QAEjC,KAAK,MAAME,YAAYJ,iBAAkB;YACvC,KAAK,MAAMK,kBAAkBD,SAASE,IAAI,CAACC,OAAO,CAAE;gBAClD,MAAM,EACJC,IAAI,EACJF,MAAM,EAAEG,IAAI,EAAE,EACf,GAAGJ;gBAEJ,4EAA4E;gBAC5E,MAAMK,MAAMC,kBAAkBH,MAAMC;gBACpC,IAAIR,6BAA6BW,GAAG,CAACF,MAAM;oBACzCG,QAAQC,IAAI,CAAC,CAAC,kBAAkB,EAAEN,KAAK,iBAAiB,EAAEC,MAAM;gBAClE;gBACAR,6BAA6Bc,GAAG,CAACL,KAAKN;gBAEtC,oCAAoC;gBACpC,IAAIY,OAAOb,qBAAqBc,GAAG,CAACT;gBACpC,IAAIQ,SAASE,WAAW;oBACtBF,OAAO,EAAE;oBACTb,qBAAqBY,GAAG,CAACP,MAAMQ;gBACjC;gBACAA,KAAKG,IAAI,CAAC;oBAAE,GAAGd,cAAc;oBAAEe,QAAQhB,SAASiB,QAAQ;gBAAC;YAC3D;QACF;QAEA,OAAO;YACLpB;YACAE;QACF;IACF;IAEA,2EAA2E;IAC3E,MAAMmB,qBAAqB1B,OAA2CsB;IACtE,MAAMK,mBAAmB5B,YAAY;QACnC,IAAI6B,UAAUF,mBAAmBG,OAAO;QACxC,IAAID,YAAYN,WAAW;YACzBM,UAAUzB;YACVuB,mBAAmBG,OAAO,GAAGD;YAE7B,2EAA2E;YAC3EA,QAAQE,KAAK,CAAC,IAAMJ,mBAAmBG,OAAO,KAAKP;QACrD;QACA,OAAOM;IACT,GAAG;QAACzB;KAAoB;IAExB,OAAOwB;AACT;AAEA;;CAEC,GACD,OAAO,SAASZ,kBAAkBH,IAAgB,EAAEC,IAAY;IAC9D,OAAO,GAAGD,KAAK,CAAC,EAAEC,MAAM;AAC1B"}
|
|
@@ -7,8 +7,9 @@ interface TimeRangeControlsProps {
|
|
|
7
7
|
showRefreshButton?: boolean;
|
|
8
8
|
showRefreshInterval?: boolean;
|
|
9
9
|
showCustomTimeRange?: boolean;
|
|
10
|
+
showZoomButtons?: boolean;
|
|
10
11
|
timePresets?: TimeOption[];
|
|
11
12
|
}
|
|
12
|
-
export declare function TimeRangeControls({ heightPx, showTimeRangeSelector, showRefreshButton, showRefreshInterval, showCustomTimeRange, timePresets, }: TimeRangeControlsProps): ReactElement;
|
|
13
|
+
export declare function TimeRangeControls({ heightPx, showTimeRangeSelector, showRefreshButton, showRefreshInterval, showCustomTimeRange, showZoomButtons, timePresets, }: TimeRangeControlsProps): ReactElement;
|
|
13
14
|
export {};
|
|
14
15
|
//# sourceMappingURL=TimeRangeControls.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimeRangeControls.d.ts","sourceRoot":"","sources":["../../../src/components/TimeRangeControls/TimeRangeControls.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TimeRangeControls.d.ts","sourceRoot":"","sources":["../../../src/components/TimeRangeControls/TimeRangeControls.tsx"],"names":[],"mappings":"AAmBA,OAAO,EAGL,UAAU,EAIX,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,YAAY,EAAe,MAAM,OAAO,CAAC;AASlD,eAAO,MAAM,gCAAgC,EAAE,UAAU,EAOxD,CAAC;AAIF,UAAU,sBAAsB;IAE9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC5B;AAED,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,qBAA4B,EAC5B,iBAAwB,EACxB,mBAA0B,EAC1B,mBAAmB,EACnB,eAAsB,EACtB,WAAW,GACZ,EAAE,sBAAsB,GAAG,YAAY,CA0IvC"}
|
|
@@ -12,11 +12,16 @@
|
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
14
14
|
import RefreshIcon from 'mdi-material-ui/Refresh';
|
|
15
|
+
// eslint-disable-next-line import/no-duplicates
|
|
16
|
+
import ZoomIn from 'mdi-material-ui/PlusCircleOutline';
|
|
17
|
+
// eslint-disable-next-line import/no-duplicates
|
|
18
|
+
import ZoomOut from 'mdi-material-ui/MinusCircleOutline';
|
|
15
19
|
import { Stack } from '@mui/material';
|
|
16
20
|
import { RefreshIntervalPicker, InfoTooltip, ToolbarIconButton, TimeRangeSelector, buildRelativeTimeOption } from '@perses-dev/components';
|
|
21
|
+
import { parseDurationString } from '@perses-dev/core';
|
|
17
22
|
import { useCallback } from 'react';
|
|
18
23
|
import { TOOLTIP_TEXT } from '../../constants';
|
|
19
|
-
import { useTimeRange, useShowCustomTimeRangeSetting, useTimeRangeOptionsSetting } from '../../runtime';
|
|
24
|
+
import { useTimeRange, useShowCustomTimeRangeSetting, useTimeRangeOptionsSetting, useShowZoomRangeSetting } from '../../runtime';
|
|
20
25
|
export const DEFAULT_REFRESH_INTERVAL_OPTIONS = [
|
|
21
26
|
{
|
|
22
27
|
value: {
|
|
@@ -56,9 +61,10 @@ export const DEFAULT_REFRESH_INTERVAL_OPTIONS = [
|
|
|
56
61
|
}
|
|
57
62
|
];
|
|
58
63
|
const DEFAULT_HEIGHT = '34px';
|
|
59
|
-
export function TimeRangeControls({ heightPx, showTimeRangeSelector = true, showRefreshButton = true, showRefreshInterval = true, showCustomTimeRange, timePresets }) {
|
|
64
|
+
export function TimeRangeControls({ heightPx, showTimeRangeSelector = true, showRefreshButton = true, showRefreshInterval = true, showCustomTimeRange, showZoomButtons = true, timePresets }) {
|
|
60
65
|
const { timeRange, setTimeRange, refresh, refreshInterval, setRefreshInterval } = useTimeRange();
|
|
61
66
|
const showCustomTimeRangeValue = useShowCustomTimeRangeSetting(showCustomTimeRange);
|
|
67
|
+
const showZoomInOutButtons = useShowZoomRangeSetting(showZoomButtons);
|
|
62
68
|
const timePresetsValue = useTimeRangeOptionsSetting(timePresets);
|
|
63
69
|
// Convert height to a string, then use the string for styling
|
|
64
70
|
const height = heightPx === undefined ? DEFAULT_HEIGHT : `${heightPx}px`;
|
|
@@ -72,6 +78,70 @@ export function TimeRangeControls({ heightPx, showTimeRangeSelector = true, show
|
|
|
72
78
|
}, [
|
|
73
79
|
setRefreshInterval
|
|
74
80
|
]);
|
|
81
|
+
const fromDurationToMillis = (strDuration)=>{
|
|
82
|
+
const duration = parseDurationString(strDuration);
|
|
83
|
+
const millis = // eslint-disable-next-line prettier/prettier
|
|
84
|
+
((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
|
|
85
|
+
(duration.years ?? 0) * 365.2425 * 86400) * // avg year duration is ok for zoom purposes
|
|
86
|
+
// eslint-disable-next-line prettier/prettier
|
|
87
|
+
1000; // to milliseconds
|
|
88
|
+
return millis;
|
|
89
|
+
};
|
|
90
|
+
// Function to double current time range, adding 50% before current start and 50% after current end
|
|
91
|
+
const doubleTimeRange = ()=>{
|
|
92
|
+
let newStart, newEnd, extendEndsBy;
|
|
93
|
+
const now = new Date();
|
|
94
|
+
if (Object.hasOwn(timeRange, 'start')) {
|
|
95
|
+
// current range is absolute
|
|
96
|
+
const absVal = timeRange;
|
|
97
|
+
extendEndsBy = (absVal.end.getTime() - absVal.start.getTime()) / 2; // half it to add 50% before current start and after current end
|
|
98
|
+
newStart = new Date(absVal.start.getTime() - extendEndsBy);
|
|
99
|
+
newEnd = new Date(absVal.end.getTime() + extendEndsBy);
|
|
100
|
+
} else {
|
|
101
|
+
// current range is relative
|
|
102
|
+
const relVal = timeRange;
|
|
103
|
+
extendEndsBy = fromDurationToMillis(relVal.pastDuration) / 2;
|
|
104
|
+
newEnd = typeof relVal.end === 'undefined' ? now : new Date(relVal.end.getTime() + extendEndsBy);
|
|
105
|
+
newStart = new Date(newEnd.getTime() - extendEndsBy * 4);
|
|
106
|
+
}
|
|
107
|
+
if (newEnd.getTime() > now.getTime()) {
|
|
108
|
+
// if the new computed end is in the future
|
|
109
|
+
newEnd = now;
|
|
110
|
+
newStart.setTime(now.getTime() - extendEndsBy * 4);
|
|
111
|
+
}
|
|
112
|
+
if (newStart.getTime() < 1) {
|
|
113
|
+
newStart.setTime(1);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
start: newStart,
|
|
117
|
+
end: newEnd
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
// Function to half current time range, cutting 25% before current start and 25% after current end
|
|
121
|
+
const halfTimeRange = ()=>{
|
|
122
|
+
let newStart, newEnd;
|
|
123
|
+
if (Object.hasOwn(timeRange, 'start')) {
|
|
124
|
+
const absVal = timeRange;
|
|
125
|
+
const shrinkEndsBy = (absVal.end.getTime() - absVal.start.getTime()) / 4;
|
|
126
|
+
newStart = new Date(absVal.start.getTime() + shrinkEndsBy);
|
|
127
|
+
newEnd = new Date(absVal.end.getTime() - shrinkEndsBy);
|
|
128
|
+
} else {
|
|
129
|
+
const relVal = timeRange;
|
|
130
|
+
const shrinkEndsBy = fromDurationToMillis(relVal.pastDuration) / 4; // 25% of it to cut after current start and before current end
|
|
131
|
+
const endIsAbsolute = typeof relVal.end !== 'undefined';
|
|
132
|
+
newEnd = endIsAbsolute ? new Date(relVal.end.getTime() - shrinkEndsBy) : new Date();
|
|
133
|
+
newStart = new Date(newEnd.getTime() - shrinkEndsBy * 2);
|
|
134
|
+
}
|
|
135
|
+
if (newStart.getTime() >= newEnd.getTime() - 1000) {
|
|
136
|
+
newStart.setTime(newEnd.getTime() - 1000);
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
start: newStart,
|
|
140
|
+
end: newEnd
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
const setHalfTimeRange = ()=>setTimeRange(halfTimeRange());
|
|
144
|
+
const setDoubleTimeRange = ()=>setTimeRange(doubleTimeRange());
|
|
75
145
|
return /*#__PURE__*/ _jsxs(Stack, {
|
|
76
146
|
direction: "row",
|
|
77
147
|
spacing: 1,
|
|
@@ -83,6 +153,28 @@ export function TimeRangeControls({ heightPx, showTimeRangeSelector = true, show
|
|
|
83
153
|
height: height,
|
|
84
154
|
showCustomTimeRange: showCustomTimeRangeValue
|
|
85
155
|
}),
|
|
156
|
+
showZoomInOutButtons && /*#__PURE__*/ _jsx(InfoTooltip, {
|
|
157
|
+
description: TOOLTIP_TEXT.zoomOut,
|
|
158
|
+
children: /*#__PURE__*/ _jsx(ToolbarIconButton, {
|
|
159
|
+
"aria-label": TOOLTIP_TEXT.zoomOut,
|
|
160
|
+
onClick: setDoubleTimeRange,
|
|
161
|
+
sx: {
|
|
162
|
+
height
|
|
163
|
+
},
|
|
164
|
+
children: /*#__PURE__*/ _jsx(ZoomOut, {})
|
|
165
|
+
})
|
|
166
|
+
}),
|
|
167
|
+
showZoomInOutButtons && /*#__PURE__*/ _jsx(InfoTooltip, {
|
|
168
|
+
description: TOOLTIP_TEXT.zoomIn,
|
|
169
|
+
children: /*#__PURE__*/ _jsx(ToolbarIconButton, {
|
|
170
|
+
"aria-label": TOOLTIP_TEXT.zoomIn,
|
|
171
|
+
onClick: setHalfTimeRange,
|
|
172
|
+
sx: {
|
|
173
|
+
height
|
|
174
|
+
},
|
|
175
|
+
children: /*#__PURE__*/ _jsx(ZoomIn, {})
|
|
176
|
+
})
|
|
177
|
+
}),
|
|
86
178
|
showRefreshButton && /*#__PURE__*/ _jsx(InfoTooltip, {
|
|
87
179
|
description: TOOLTIP_TEXT.refresh,
|
|
88
180
|
children: /*#__PURE__*/ _jsx(ToolbarIconButton, {
|