From be5164f74e77a649895b1517fd25c5bd5f3456f4 Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Fri, 17 Jul 2026 16:36:47 +0200 Subject: [PATCH 1/4] [ENHANCEMENT] prometheus: add instant mode to queries Signed-off-by: Markus Wieland --- .../migrate/migrate.cue | 4 ++ .../migrate/tests/instant/expected.json | 7 ++++ .../migrate/tests/instant/input.json | 5 +++ .../prometheus-time-series-query/query.cue | 1 + .../prometheus-time-series-query-instant.json | 12 ++++++ prometheus/sdk/go/query/options.go | 7 ++++ prometheus/sdk/go/query/query.go | 1 + .../PrometheusTimeSeriesQueryEditor.tsx | 15 +++++++- .../get-time-series-data.ts | 13 +++---- .../plugin.test.ts | 38 ++++++++++++++++++- .../query-editor-model.ts | 23 +++++++++++ .../time-series-query-model.ts | 1 + 12 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 prometheus/schemas/prometheus-time-series-query/migrate/tests/instant/expected.json create mode 100644 prometheus/schemas/prometheus-time-series-query/migrate/tests/instant/input.json create mode 100644 prometheus/schemas/prometheus-time-series-query/tests/valid/prometheus-time-series-query-instant.json diff --git a/prometheus/schemas/prometheus-time-series-query/migrate/migrate.cue b/prometheus/schemas/prometheus-time-series-query/migrate/migrate.cue index 68a7e2229..81c8bfc2d 100644 --- a/prometheus/schemas/prometheus-time-series-query/migrate/migrate.cue +++ b/prometheus/schemas/prometheus-time-series-query/migrate/migrate.cue @@ -25,6 +25,7 @@ package migrate expr: string legendFormat?: string interval?: string + instant?: bool ... } @@ -44,4 +45,7 @@ spec: { if #target.interval != _|_ { minStep: #target.interval } + if #target.instant != _|_ { + instant: #target.instant + } } diff --git a/prometheus/schemas/prometheus-time-series-query/migrate/tests/instant/expected.json b/prometheus/schemas/prometheus-time-series-query/migrate/tests/instant/expected.json new file mode 100644 index 000000000..baea123b5 --- /dev/null +++ b/prometheus/schemas/prometheus-time-series-query/migrate/tests/instant/expected.json @@ -0,0 +1,7 @@ +{ + "kind": "PrometheusTimeSeriesQuery", + "spec": { + "query": "up{env=\"demo\",instance=~\"demo.do.prometheus.io.*\"}", + "instant": true + } +} diff --git a/prometheus/schemas/prometheus-time-series-query/migrate/tests/instant/input.json b/prometheus/schemas/prometheus-time-series-query/migrate/tests/instant/input.json new file mode 100644 index 000000000..ddd0c893a --- /dev/null +++ b/prometheus/schemas/prometheus-time-series-query/migrate/tests/instant/input.json @@ -0,0 +1,5 @@ +{ + "expr": "up{env=\"demo\",instance=~\"demo.do.prometheus.io.*\"}", + "instant": true, + "refID": "A" +} diff --git a/prometheus/schemas/prometheus-time-series-query/query.cue b/prometheus/schemas/prometheus-time-series-query/query.cue index 13efd1b49..ec4a62ad9 100644 --- a/prometheus/schemas/prometheus-time-series-query/query.cue +++ b/prometheus/schemas/prometheus-time-series-query/query.cue @@ -26,6 +26,7 @@ spec: close({ seriesNameFormat?: string minStep?: =~ds.#durationRegex | =~common.#variableSyntaxRegex resolution?: number + instant?: bool }) #variableSyntaxRegex: "^\\$\\w+$" diff --git a/prometheus/schemas/prometheus-time-series-query/tests/valid/prometheus-time-series-query-instant.json b/prometheus/schemas/prometheus-time-series-query/tests/valid/prometheus-time-series-query-instant.json new file mode 100644 index 000000000..3213bd145 --- /dev/null +++ b/prometheus/schemas/prometheus-time-series-query/tests/valid/prometheus-time-series-query-instant.json @@ -0,0 +1,12 @@ +{ + "kind": "PrometheusTimeSeriesQuery", + "spec": { + "datasource": { + "kind": "PrometheusDatasource", + "name": "MyDemoDatasource" + }, + "query": "max by (job, instance) (up)", + "seriesNameFormat": "{{ job }}/{{ instance }}", + "instant": true + } +} diff --git a/prometheus/sdk/go/query/options.go b/prometheus/sdk/go/query/options.go index 5ab37e8a4..fed4bc46b 100644 --- a/prometheus/sdk/go/query/options.go +++ b/prometheus/sdk/go/query/options.go @@ -54,3 +54,10 @@ func Resolution(resolution int) Option { return nil } } + +func Instant(instant bool) Option { + return func(builder *Builder) error { + builder.Instant = instant + return nil + } +} diff --git a/prometheus/sdk/go/query/query.go b/prometheus/sdk/go/query/query.go index bbe273b75..064b0d020 100644 --- a/prometheus/sdk/go/query/query.go +++ b/prometheus/sdk/go/query/query.go @@ -28,6 +28,7 @@ type PluginSpec struct { SeriesNameFormat string `json:"seriesNameFormat,omitempty" yaml:"seriesNameFormat,omitempty"` MinStep common.Duration `json:"minStep,omitempty" yaml:"minStep,omitempty"` Resolution int `json:"resolution,omitempty" yaml:"resolution,omitempty"` + Instant bool `json:"instant,omitempty" yaml:"instant,omitempty"` } type Option func(plugin *Builder) error diff --git a/prometheus/src/plugins/prometheus-time-series-query/PrometheusTimeSeriesQueryEditor.tsx b/prometheus/src/plugins/prometheus-time-series-query/PrometheusTimeSeriesQueryEditor.tsx index 5a75faf24..8e1da6df0 100644 --- a/prometheus/src/plugins/prometheus-time-series-query/PrometheusTimeSeriesQueryEditor.tsx +++ b/prometheus/src/plugins/prometheus-time-series-query/PrometheusTimeSeriesQueryEditor.tsx @@ -24,7 +24,7 @@ import { useTimeRange, } from '@perses-dev/plugin-system'; import { useId } from '@perses-dev/components'; -import { FormControl, Stack, TextField } from '@mui/material'; +import { Button, ButtonGroup, FormControl, Stack, TextField } from '@mui/material'; import { ReactElement, useContext, useMemo } from 'react'; import { PanelEditorContext } from '@perses-dev/dashboards'; import { @@ -46,6 +46,7 @@ import { useQueryState, useFormatState, useMinStepState, + useInstantState, } from './query-editor-model'; /** * The options editor component for editing a PrometheusTimeSeriesQuery's spec. @@ -74,6 +75,7 @@ export function PrometheusTimeSeriesQueryEditor(props: PrometheusTimeSeriesQuery const { handleQueryChange, handleQueryBlur } = useQueryState(props); const { format, handleFormatChange, handleFormatBlur } = useFormatState(props); const { minStep, handleMinStepChange, handleMinStepBlur } = useMinStepState(props); + const { instant, handleInstantChange } = useInstantState(props); const minStepPlaceholder = minStep ?? (datasourceResource && (datasourceResource?.plugin.spec as PrometheusDatasourceSpec).scrapeInterval) ?? @@ -151,7 +153,7 @@ export function PrometheusTimeSeriesQueryEditor(props: PrometheusTimeSeriesQuery isReadOnly={isReadonly} treeViewMetadata={treeViewMetadata} /> - + handleMinStepChange(e.target.value ? (e.target.value as DurationString) : undefined)} onBlur={handleMinStepBlur} + disabled={instant} sx={{ width: '250px' }} slotProps={{ inputLabel: { shrink: isReadonly ? true : undefined }, input: { readOnly: isReadonly }, }} /> + + + + ); diff --git a/prometheus/src/plugins/prometheus-time-series-query/get-time-series-data.ts b/prometheus/src/plugins/prometheus-time-series-query/get-time-series-data.ts index 6b480ade2..68842dd02 100644 --- a/prometheus/src/plugins/prometheus-time-series-query/get-time-series-data.ts +++ b/prometheus/src/plugins/prometheus-time-series-query/get-time-series-data.ts @@ -119,14 +119,11 @@ export const getTimeSeriesData: TimeSeriesQueryPlugin { return stubRepsonse; }); +// Mock instant query +promStubClient.instantQuery = jest.fn(async () => { + const stubResponse: InstantQueryResponse = { + status: 'success', + data: { + resultType: 'vector', + result: [ + { + metric: { + __name__: 'up', + }, + value: [1686141338.877, '10'], + }, + ], + }, + }; + return stubResponse; +}); + const getDatasourceClient: jest.Mock = jest.fn(() => { return promStubClient; }); @@ -114,4 +133,21 @@ describe('PrometheusTimeSeriesQuery', () => { expect(results.series[0]?.formattedName).toEqual('bar - format'); }); + + it('should use instantQuery when spec.instant is true', async () => { + const ctx = createStubContext(); + (promStubClient.instantQuery as jest.Mock).mockClear(); + (promStubClient.rangeQuery as jest.Mock).mockClear(); + + await PrometheusTimeSeriesQuery.getTimeSeriesData( + { + query: 'up', + instant: true, + }, + ctx + ); + + expect(promStubClient.instantQuery).toHaveBeenCalledTimes(1); + expect(promStubClient.rangeQuery).not.toHaveBeenCalled(); + }); }); diff --git a/prometheus/src/plugins/prometheus-time-series-query/query-editor-model.ts b/prometheus/src/plugins/prometheus-time-series-query/query-editor-model.ts index e7581ab10..676b54a58 100644 --- a/prometheus/src/plugins/prometheus-time-series-query/query-editor-model.ts +++ b/prometheus/src/plugins/prometheus-time-series-query/query-editor-model.ts @@ -133,3 +133,26 @@ export function useMinStepState(props: PrometheusTimeSeriesQueryEditorProps): { return { minStep, handleMinStepChange, handleMinStepBlur }; } + +/** + * Hook to manage `instant` state. Since a checkbox/switch commits immediately, changes are propagated to the spec on + * toggle rather than on blur. + */ +export function useInstantState(props: PrometheusTimeSeriesQueryEditorProps): { + instant: boolean; + handleInstantChange: (instant: boolean) => void; +} { + const { onChange, value } = props; + + const instant = value.instant ?? false; + + const handleInstantChange = (next: boolean): void => { + onChange( + produce(value, (draft) => { + draft.instant = next; + }) + ); + }; + + return { instant, handleInstantChange }; +} diff --git a/prometheus/src/plugins/prometheus-time-series-query/time-series-query-model.ts b/prometheus/src/plugins/prometheus-time-series-query/time-series-query-model.ts index 8dc68f213..e0b75a153 100644 --- a/prometheus/src/plugins/prometheus-time-series-query/time-series-query-model.ts +++ b/prometheus/src/plugins/prometheus-time-series-query/time-series-query-model.ts @@ -22,5 +22,6 @@ export interface PrometheusTimeSeriesQuerySpec { seriesNameFormat?: string; minStep?: DurationString; resolution?: number; + instant?: boolean; datasource?: DatasourceSelectValue; } From 5770f1f387f8bbb7db386c80bc4a86045c8a46b4 Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Fri, 24 Jul 2026 15:24:32 +0200 Subject: [PATCH 2/4] [ENHANCEMENT] prometheus: refine instant mode handling and add tests Signed-off-by: Markus Wieland --- .../PrometheusTimeSeriesQueryEditor.tsx | 9 +++-- .../get-time-series-data.ts | 4 ++- .../plugin.test.ts | 35 +++++++++++++++++++ .../query-editor-model.ts | 8 ++--- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/prometheus/src/plugins/prometheus-time-series-query/PrometheusTimeSeriesQueryEditor.tsx b/prometheus/src/plugins/prometheus-time-series-query/PrometheusTimeSeriesQueryEditor.tsx index 8e1da6df0..d199feb70 100644 --- a/prometheus/src/plugins/prometheus-time-series-query/PrometheusTimeSeriesQueryEditor.tsx +++ b/prometheus/src/plugins/prometheus-time-series-query/PrometheusTimeSeriesQueryEditor.tsx @@ -174,7 +174,7 @@ export function PrometheusTimeSeriesQueryEditor(props: PrometheusTimeSeriesQuery value={minStep ?? ''} onChange={(e) => handleMinStepChange(e.target.value ? (e.target.value as DurationString) : undefined)} onBlur={handleMinStepBlur} - disabled={instant} + disabled={instant === true} sx={{ width: '250px' }} slotProps={{ inputLabel: { shrink: isReadonly ? true : undefined }, @@ -182,12 +182,15 @@ export function PrometheusTimeSeriesQueryEditor(props: PrometheusTimeSeriesQuery }} /> - - + diff --git a/prometheus/src/plugins/prometheus-time-series-query/get-time-series-data.ts b/prometheus/src/plugins/prometheus-time-series-query/get-time-series-data.ts index 68842dd02..e52102945 100644 --- a/prometheus/src/plugins/prometheus-time-series-query/get-time-series-data.ts +++ b/prometheus/src/plugins/prometheus-time-series-query/get-time-series-data.ts @@ -119,7 +119,9 @@ export const getTimeSeriesData: TimeSeriesQueryPlugin { expect(promStubClient.instantQuery).toHaveBeenCalledTimes(1); expect(promStubClient.rangeQuery).not.toHaveBeenCalled(); }); + + it('should use rangeQuery when spec.instant is false even if context mode is instant', async () => { + const ctx = createStubContext(); + ctx.mode = 'instant'; + (promStubClient.instantQuery as jest.Mock).mockClear(); + (promStubClient.rangeQuery as jest.Mock).mockClear(); + + await PrometheusTimeSeriesQuery.getTimeSeriesData( + { + query: 'up', + instant: false, + }, + ctx + ); + + expect(promStubClient.rangeQuery).toHaveBeenCalledTimes(1); + expect(promStubClient.instantQuery).not.toHaveBeenCalled(); + }); + + it('should use instantQuery when spec.instant is unset and context mode is instant', async () => { + const ctx = createStubContext(); + ctx.mode = 'instant'; + (promStubClient.instantQuery as jest.Mock).mockClear(); + (promStubClient.rangeQuery as jest.Mock).mockClear(); + + await PrometheusTimeSeriesQuery.getTimeSeriesData( + { + query: 'up', + }, + ctx + ); + + expect(promStubClient.instantQuery).toHaveBeenCalledTimes(1); + expect(promStubClient.rangeQuery).not.toHaveBeenCalled(); + }); }); diff --git a/prometheus/src/plugins/prometheus-time-series-query/query-editor-model.ts b/prometheus/src/plugins/prometheus-time-series-query/query-editor-model.ts index 676b54a58..e0a8d5912 100644 --- a/prometheus/src/plugins/prometheus-time-series-query/query-editor-model.ts +++ b/prometheus/src/plugins/prometheus-time-series-query/query-editor-model.ts @@ -139,14 +139,14 @@ export function useMinStepState(props: PrometheusTimeSeriesQueryEditorProps): { * toggle rather than on blur. */ export function useInstantState(props: PrometheusTimeSeriesQueryEditorProps): { - instant: boolean; - handleInstantChange: (instant: boolean) => void; + instant: boolean | undefined; + handleInstantChange: (instant: boolean | undefined) => void; } { const { onChange, value } = props; - const instant = value.instant ?? false; + const instant = value.instant; - const handleInstantChange = (next: boolean): void => { + const handleInstantChange = (next: boolean | undefined): void => { onChange( produce(value, (draft) => { draft.instant = next; From c5f3273574b23f3f10961d0eab0c6e8d216dd12b Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Fri, 24 Jul 2026 15:41:57 +0200 Subject: [PATCH 3/4] [FIX] prometheus: linter issues Signed-off-by: Markus Wieland --- prometheus/src/plugins/PrometheusLabelValuesVariable.tsx | 3 ++- .../PrometheusTimeSeriesQueryEditor.tsx | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/prometheus/src/plugins/PrometheusLabelValuesVariable.tsx b/prometheus/src/plugins/PrometheusLabelValuesVariable.tsx index ec64d4142..28da8f49c 100644 --- a/prometheus/src/plugins/PrometheusLabelValuesVariable.tsx +++ b/prometheus/src/plugins/PrometheusLabelValuesVariable.tsx @@ -87,7 +87,8 @@ function getDatasourceVariablesFromCache( if (!datasourceStore.getDatasourceSpecSync) return []; const datasourceSpec = datasourceStore.getDatasourceSpecSync(datasourceSelector) as - DatasourceSpec | undefined; + | DatasourceSpec + | undefined; return datasourceSpec ? extractDatasourceVariables(datasourceSpec) : []; } catch { return []; diff --git a/prometheus/src/plugins/prometheus-time-series-query/PrometheusTimeSeriesQueryEditor.tsx b/prometheus/src/plugins/prometheus-time-series-query/PrometheusTimeSeriesQueryEditor.tsx index d199feb70..fbddd8945 100644 --- a/prometheus/src/plugins/prometheus-time-series-query/PrometheusTimeSeriesQueryEditor.tsx +++ b/prometheus/src/plugins/prometheus-time-series-query/PrometheusTimeSeriesQueryEditor.tsx @@ -188,7 +188,10 @@ export function PrometheusTimeSeriesQueryEditor(props: PrometheusTimeSeriesQuery - From e958c466086d4de6a1a4af02efda50b6e47365cb Mon Sep 17 00:00:00 2001 From: Markus Wieland Date: Fri, 24 Jul 2026 15:53:26 +0200 Subject: [PATCH 4/4] [FIX] prometheus: linter issues Signed-off-by: Markus Wieland --- prometheus/src/plugins/PrometheusLabelValuesVariable.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/prometheus/src/plugins/PrometheusLabelValuesVariable.tsx b/prometheus/src/plugins/PrometheusLabelValuesVariable.tsx index 28da8f49c..ec64d4142 100644 --- a/prometheus/src/plugins/PrometheusLabelValuesVariable.tsx +++ b/prometheus/src/plugins/PrometheusLabelValuesVariable.tsx @@ -87,8 +87,7 @@ function getDatasourceVariablesFromCache( if (!datasourceStore.getDatasourceSpecSync) return []; const datasourceSpec = datasourceStore.getDatasourceSpecSync(datasourceSelector) as - | DatasourceSpec - | undefined; + DatasourceSpec | undefined; return datasourceSpec ? extractDatasourceVariables(datasourceSpec) : []; } catch { return [];