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..fbddd8945 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 === true}
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..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,14 +119,13 @@ 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,56 @@ 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();
+ });
+
+ 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 e7581ab10..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
@@ -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 | undefined;
+ handleInstantChange: (instant: boolean | undefined) => void;
+} {
+ const { onChange, value } = props;
+
+ const instant = value.instant;
+
+ const handleInstantChange = (next: boolean | undefined): 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;
}