Skip to content

Commit 7b162aa

Browse files
committed
[FEATURE]: Adds support for log type y-axis (as well as linear) for TimeSeriesChart
- adds possibility for choosing the "ECharts yaxis type:log" field instead of just 'value'. - adds options for choosing log-base (2, 10) - adds cue and updates go-sdk with logbase --------- on-behalf-of: @SAP Simon Olander (simon.olander@sap.com)
1 parent 82f4db1 commit 7b162aa

File tree

10 files changed

+257
-25
lines changed

10 files changed

+257
-25
lines changed

timeserieschart/schemas/migrate/migrate.cue

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,17 @@ spec: {
106106
yAxis: max: #max
107107
}
108108

109+
#logBase: [// switch
110+
if (*#panel.fieldConfig.defaults.logBase | null) != null {
111+
#panel.fieldConfig.defaults.logBase
112+
},
113+
null,
114+
][0]
115+
if #logBase != null {
116+
yAxis: logBase: #logBase
117+
yAxis: type: "log"
118+
}
119+
109120
#yAxisLabel: *#panel.fieldConfig.defaults.custom.axisLabel | null
110121
if #yAxisLabel != null if len(#yAxisLabel) > 0 {
111122
yAxis: label: #yAxisLabel
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"kind": "TimeSeriesChart",
3+
"spec": {
4+
"legend": {
5+
"mode": "list",
6+
"position": "bottom",
7+
"values": []
8+
},
9+
"visual": {
10+
"areaOpacity": 0.1,
11+
"connectNulls": false,
12+
"display": "line",
13+
"lineWidth": 1,
14+
"lineStyle": "solid"
15+
},
16+
"yAxis": {
17+
"format": {
18+
"unit": "bytes"
19+
},
20+
"label": "Amount of endpoints succesfully monitored",
21+
"min": 0,
22+
"logBase": 2,
23+
"type": "log"
24+
},
25+
"thresholds": {
26+
"steps": [
27+
{
28+
"color": "#73bf69",
29+
"value": 0
30+
},
31+
{
32+
"color": "#f2cc0c",
33+
"value": 80
34+
}
35+
]
36+
}
37+
}
38+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
{
2+
"datasource": {
3+
"type": "prometheus",
4+
"uid": "${datasource}"
5+
},
6+
"description": "Current raw capacity of DataNode in bytes",
7+
"fieldConfig": {
8+
"defaults": {
9+
"color": {
10+
"mode": "palette-classic"
11+
},
12+
"custom": {
13+
"axisLabel": "Amount of endpoints succesfully monitored",
14+
"barAlignment": 0,
15+
"barWidthFactor": 0.6,
16+
"drawStyle": "line",
17+
"fillOpacity": 10,
18+
"gradientMode": "none",
19+
"hideFrom": {
20+
"legend": false,
21+
"tooltip": false,
22+
"viz": false
23+
},
24+
"insertNulls": false,
25+
"lineInterpolation": "linear",
26+
"lineWidth": 1,
27+
"lineStyle": {
28+
"fill": "solid"
29+
},
30+
"pointSize": 5,
31+
"scaleDistribution": {
32+
"type": "linear"
33+
},
34+
"showPoints": "never",
35+
"spanNulls": false,
36+
"stacking": {
37+
"group": "A",
38+
"mode": "none"
39+
},
40+
"thresholdsStyle": {
41+
"mode": "line"
42+
}
43+
},
44+
"links": [],
45+
"mappings": [],
46+
"min": 0,
47+
"logBase": 2,
48+
"thresholds": {
49+
"mode": "absolute",
50+
"steps": [
51+
{
52+
"color": "green",
53+
"value": null
54+
},
55+
{
56+
"color": "semi-dark-yellow",
57+
"value": 80
58+
}
59+
]
60+
},
61+
"unit": "bytes"
62+
},
63+
"overrides": []
64+
},
65+
"gridPos": {
66+
"h": 7,
67+
"w": 6,
68+
"x": 0,
69+
"y": 0
70+
},
71+
"id": 27,
72+
"options": {
73+
"legend": {
74+
"calcs": [],
75+
"displayMode": "list",
76+
"placement": "bottom",
77+
"showLegend": true
78+
},
79+
"tooltip": {
80+
"mode": "multi",
81+
"sort": "desc"
82+
}
83+
},
84+
"pluginVersion": "11.3.0-pre",
85+
"targets": [
86+
{
87+
"datasource": {
88+
"uid": "${datasource}"
89+
},
90+
"editorMode": "code",
91+
"expr": "avg without (instance) (hadoop_hdfs_datanode_capacity{bar_stack_id=\"$bar_stack_id\", _target=~\"$datanode\"})",
92+
"interval": "",
93+
"legendFormat": "{{_target}}",
94+
"range": true,
95+
"refId": "A"
96+
}
97+
],
98+
"title": "datanode_capacity",
99+
"type": "timeseries"
100+
}

timeserieschart/schemas/time-series.cue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ spec: close({
5656
if min != _|_ && max != _|_ {
5757
max: >=min
5858
}
59+
logBase?: 2 | 10
5960
}
6061

6162
#querySettings: [...{
@@ -67,4 +68,4 @@ spec: close({
6768
}]
6869

6970
#lineStyle: "solid" | "dashed" | "dotted"
70-
#areaOpacity: number & >=0 & <=1 // transparency level from 0 (transparent) to 1 (opaque)
71+
#areaOpacity: number & >=0 & <=1 // transparency level from 0 (transparent) to 1 (opaque)

timeserieschart/sdk/go/time-series.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,12 @@ type Visual struct {
9696
}
9797

9898
type YAxis struct {
99-
Show bool `json:"show,omitempty" yaml:"show,omitempty"`
100-
Label string `json:"label,omitempty" yaml:"label,omitempty"`
101-
Format *common.Format `json:"format,omitempty" yaml:"format,omitempty"`
102-
Min float64 `json:"min,omitempty" yaml:"min,omitempty"`
103-
Max float64 `json:"max,omitempty" yaml:"max,omitempty"`
99+
Show bool `json:"show,omitempty" yaml:"show,omitempty"`
100+
Label string `json:"label,omitempty" yaml:"label,omitempty"`
101+
Format *common.Format `json:"format,omitempty" yaml:"format,omitempty"`
102+
Min float64 `json:"min,omitempty" yaml:"min,omitempty"`
103+
Max float64 `json:"max,omitempty" yaml:"max,omitempty"`
104+
LogBase uint `json:"logBase,omitempty" yaml:"logBase,omitempty"`
104105
}
105106

106107
type PluginSpec struct {

timeserieschart/src/TimeSeriesChartPanel.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import {
5454
DEFAULT_VISUAL,
5555
THRESHOLD_PLOT_INTERVAL,
5656
QuerySettingsOptions,
57+
LOG_BASE,
5758
} from './time-series-chart-model';
5859
import {
5960
getTimeSeries,
@@ -121,10 +122,13 @@ export function TimeSeriesChartPanel(props: TimeSeriesChartProps): ReactElement
121122
return merge({}, DEFAULT_VISUAL, props.spec.visual);
122123
}, [props.spec.visual]);
123124

125+
// Use the logBase from yAxis options, defaulting to 'none' if not set
126+
const useLogarithmicBase: LOG_BASE = yAxis?.logBase ?? 'none';
127+
124128
// convert Perses dashboard format to be ECharts compatible
125129
const echartsYAxis = useMemo(() => {
126-
return convertPanelYAxis(yAxis);
127-
}, [yAxis]);
130+
return convertPanelYAxis(yAxis, useLogarithmicBase);
131+
}, [yAxis, useLogarithmicBase]);
128132

129133
const [selectedLegendItems, setSelectedLegendItems] = useState<SelectedLegendItemState>('ALL');
130134
const [legendSorting, setLegendSorting] = useState<NonNullable<LegendProps['tableProps']>['sorting']>();

timeserieschart/src/YAxisOptionsEditor.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,20 @@
1212
// limitations under the License.
1313

1414
import { Switch, TextField } from '@mui/material';
15-
import { OptionsEditorControl, OptionsEditorGroup, FormatControls } from '@perses-dev/components';
15+
16+
import { OptionsEditorControl, OptionsEditorGroup, FormatControls, SettingsAutocomplete } from '@perses-dev/components';
1617
import { ReactElement } from 'react';
17-
import { DEFAULT_FORMAT, DEFAULT_Y_AXIS, TimeSeriesChartYAxisOptions, Y_AXIS_CONFIG } from './time-series-chart-model';
18+
import { DEFAULT_FORMAT, DEFAULT_Y_AXIS, TimeSeriesChartYAxisOptions, Y_AXIS_CONFIG, LOG_BASE_LABEL, LOG_BASE, LOG_BASE_OPTIONS, LOG_BASE_CONFIG, LOG_VALID_BASES} from './time-series-chart-model';
1819

1920
export interface YAxisOptionsEditorProps {
2021
value: TimeSeriesChartYAxisOptions;
2122
onChange: (yAxis: TimeSeriesChartYAxisOptions) => void;
2223
}
2324

2425
export function YAxisOptionsEditor({ value, onChange }: YAxisOptionsEditorProps): ReactElement {
26+
27+
const logBase = LOG_BASE_CONFIG[LOG_VALID_BASES[value.logBase ?? 'none']];
28+
2529
return (
2630
<OptionsEditorGroup title="Y Axis">
2731
<OptionsEditorControl
@@ -47,6 +51,27 @@ export function YAxisOptionsEditor({ value, onChange }: YAxisOptionsEditorProps)
4751
})
4852
}
4953
/>
54+
<OptionsEditorControl
55+
label={Y_AXIS_CONFIG.logBase.label}
56+
control={
57+
<SettingsAutocomplete
58+
value={{
59+
...logBase,
60+
id: logBase.label,
61+
}}
62+
options={LOG_BASE_OPTIONS}
63+
onChange={(__, newValue) => {
64+
const updatedValue: TimeSeriesChartYAxisOptions = {
65+
...value,
66+
logBase: newValue.log
67+
};
68+
onChange(updatedValue);
69+
}}
70+
disabled={value === undefined}
71+
disableClearable
72+
></SettingsAutocomplete>
73+
}
74+
/>
5075
<OptionsEditorControl
5176
label={Y_AXIS_CONFIG.label.label}
5277
control={

timeserieschart/src/time-series-chart-model.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export interface TimeSeriesChartYAxisOptions {
5454
format?: FormatOptions;
5555
min?: number;
5656
max?: number;
57+
logBase?: LOG_BASE;
5758
}
5859

5960
export interface TooltipSpecOptions {
@@ -87,6 +88,7 @@ export const DEFAULT_Y_AXIS: TimeSeriesChartYAxisOptions = {
8788
format: DEFAULT_FORMAT,
8889
min: undefined,
8990
max: undefined,
91+
logBase: 'none',
9092
};
9193

9294
export const Y_AXIS_CONFIG = {
@@ -95,6 +97,7 @@ export const Y_AXIS_CONFIG = {
9597
unit: { label: 'Unit' },
9698
min: { label: 'Min' },
9799
max: { label: 'Max' },
100+
logBase: { label: 'Log Base' },
98101
};
99102

100103
export const DEFAULT_DISPLAY = 'line';
@@ -183,6 +186,28 @@ export const OPACITY_CONFIG = {
183186
step: 0.05,
184187
};
185188

189+
// LogBase outlines the allowed log bases for the log-supported charts.
190+
export type LOG_BASE_LABEL = 'none' | 'log2' | 'log10';
191+
export type LOG_BASE = 'none' | 2 | 10;
192+
193+
// Single source of truth for log base configuration
194+
export const LOG_BASE_CONFIG: Record<LOG_BASE_LABEL, { label: string; log: LOG_BASE }> = {
195+
'none': { label: 'None', log: 'none' },
196+
'log2': { label: '2', log: 2 },
197+
'log10': { label: '10', log: 10 },
198+
};
199+
200+
// Options array for SettingsAutocomplete
201+
export const LOG_BASE_OPTIONS = Object.entries(LOG_BASE_CONFIG).map(([id, config]) => ({
202+
id: id as LOG_BASE_LABEL,
203+
...config,
204+
}));
205+
206+
// Reverse lookup map from LOG_BASE value to LOG_BASE_LABEL
207+
export const LOG_VALID_BASES: Record<LOG_BASE, LOG_BASE_LABEL> = Object.fromEntries(
208+
Object.entries(LOG_BASE_CONFIG).map(([label, config]) => [config.log, label])
209+
) as Record<LOG_BASE, LOG_BASE_LABEL>;
210+
186211
// Both of these constants help produce a value that is LESS THAN the initial value.
187212
// For positive values, we multiply by a number less than 1 to get this outcome.
188213
// For negative values, we multiply to a number greater than 1 to get this outcome.

timeserieschart/src/utils/data-transform.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe('convertPanelYAxis', () => {
5454
min: 0.1,
5555
max: 1,
5656
};
57-
const echartsAxis = convertPanelYAxis(persesAxis);
57+
const echartsAxis = convertPanelYAxis(persesAxis, 'none');
5858
// Axis label is handled outside of echarts since it is built with a custom React component.
5959
expect(echartsAxis).toEqual({
6060
show: true,

0 commit comments

Comments
 (0)