Skip to content

Commit 8dcea73

Browse files
author
Iris Ye
committed
Table: gauge cells, conditional colors, range inputs
- gauge chart fixed in cell - gauge chart colors aligned with cell settings - range function debugged to allow only number (letters except e(used as scientific notation) cannot be entered instead of returning NaN) - range function allow decimals not just integers Signed-off-by: Iris Ye <iris.ye@sap.com>
1 parent 733f10c commit 8dcea73

4 files changed

Lines changed: 337 additions & 22 deletions

File tree

table/src/components/ColumnsEditor/ColumnEditor.tsx

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { PluginKindSelect } from '@perses-dev/plugin-system';
2828
import { ColumnSettings } from '../../models';
2929
import { ConditionalPanel } from '../ConditionalPanel';
3030
import { DataLinkEditor } from './DataLinkEditorDialog';
31+
import { EmbeddedPanelOptionsEditor } from './EmbeddedPanelOptionsEditor';
3132

3233
const DEFAULT_FORMAT: FormatOptions = {
3334
unit: 'decimal',
@@ -150,16 +151,31 @@ export function ColumnEditor({ column, onChange, ...others }: ColumnEditorProps)
150151
}
151152
/>
152153
{column.plugin ? (
153-
<OptionsEditorControl
154-
label="Panel Type"
155-
control={
156-
<PluginKindSelect
157-
pluginTypes={['Panel']}
158-
value={{ type: 'Panel', kind: column.plugin.kind }}
159-
onChange={(event) => onChange({ ...column, plugin: { kind: event.kind, spec: {} } })}
154+
<>
155+
<OptionsEditorControl
156+
label="Panel Type"
157+
control={
158+
<PluginKindSelect
159+
pluginTypes={['Panel']}
160+
size="small"
161+
value={{ type: 'Panel', kind: column.plugin.kind }}
162+
onChange={(event) => onChange({ ...column, plugin: { kind: event.kind, spec: {} } })}
163+
/>
164+
}
165+
/>
166+
{column.plugin.kind === 'GaugeChart' && (
167+
<EmbeddedPanelOptionsEditor
168+
kind="GaugeChart"
169+
spec={column.plugin.spec}
170+
onChange={(nextSpec) =>
171+
onChange({
172+
...column,
173+
plugin: { kind: 'GaugeChart', spec: nextSpec },
174+
})
175+
}
160176
/>
161-
}
162-
/>
177+
)}
178+
</>
163179
) : (
164180
<FormatControls
165181
value={column.format ?? DEFAULT_FORMAT}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright The Perses Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
import { CircularProgress, Stack, Typography } from '@mui/material';
15+
import { UnknownSpec } from '@perses-dev/core';
16+
import { OptionsEditorTabs, PanelPlugin, usePlugin } from '@perses-dev/plugin-system';
17+
import merge from 'lodash/merge';
18+
import { ReactElement, useEffect, useMemo, useRef } from 'react';
19+
20+
export interface EmbeddedPanelOptionsEditorProps {
21+
kind: string;
22+
spec: UnknownSpec;
23+
onChange: (next: UnknownSpec) => void;
24+
}
25+
26+
function isSpecEmpty(spec: UnknownSpec | undefined): boolean {
27+
if (spec === undefined || spec === null) return true;
28+
if (typeof spec !== 'object') return false;
29+
return Object.keys(spec as object).length === 0;
30+
}
31+
32+
function mergeWithPluginDefaults(plugin: PanelPlugin, spec: UnknownSpec | undefined): UnknownSpec {
33+
const initial = plugin.createInitialOptions() ?? {};
34+
return merge({}, initial, spec ?? {}) as UnknownSpec;
35+
}
36+
37+
/**
38+
* Renders a panel plugin's settings tabs (thresholds, units, colors, …).
39+
* Used for embedded GaugeChart columns only; other embedded panel kinds use defaults.
40+
*/
41+
export function EmbeddedPanelOptionsEditor({ kind, spec, onChange }: EmbeddedPanelOptionsEditorProps): ReactElement {
42+
const { data: plugin, isLoading, isError, error } = usePlugin('Panel', kind);
43+
44+
const panelPlugin = plugin as PanelPlugin | undefined;
45+
46+
const mergedSpec = useMemo(() => {
47+
if (!panelPlugin) {
48+
return spec;
49+
}
50+
return mergeWithPluginDefaults(panelPlugin, spec);
51+
}, [panelPlugin, spec]);
52+
53+
const onChangeRef = useRef(onChange);
54+
onChangeRef.current = onChange;
55+
56+
// Persist plugin defaults when the column still has an empty spec (e.g. after switching panel kind).
57+
useEffect(() => {
58+
if (!panelPlugin || !isSpecEmpty(spec)) {
59+
return;
60+
}
61+
onChangeRef.current(mergeWithPluginDefaults(panelPlugin, spec));
62+
}, [panelPlugin, kind, spec]);
63+
64+
if (isLoading) {
65+
return (
66+
<Stack direction="row" alignItems="center" spacing={1} sx={{ py: 1 }}>
67+
<CircularProgress size={22} />
68+
<Typography variant="body2" color="text.secondary">
69+
Loading panel settings…
70+
</Typography>
71+
</Stack>
72+
);
73+
}
74+
75+
if (isError || !plugin) {
76+
return (
77+
<Typography variant="body2" color="error">
78+
{error?.message ?? 'Could not load panel plugin.'}
79+
</Typography>
80+
);
81+
}
82+
83+
const loadedPlugin = plugin as PanelPlugin;
84+
const editorTabs = loadedPlugin.panelOptionsEditorComponents ?? [];
85+
86+
if (editorTabs.length === 0) {
87+
return (
88+
<Typography variant="body2" color="text.secondary">
89+
This visualization has no editable settings.
90+
</Typography>
91+
);
92+
}
93+
94+
return (
95+
<OptionsEditorTabs
96+
tabs={editorTabs.map((tab) => {
97+
const Content = tab.content;
98+
return {
99+
label: tab.label,
100+
content: (
101+
<Content
102+
value={mergedSpec}
103+
onChange={(next) => {
104+
onChange(next as UnknownSpec);
105+
}}
106+
/>
107+
),
108+
};
109+
})}
110+
/>
111+
);
112+
}

0 commit comments

Comments
 (0)