From e57dd7f45ab28f93b529ec60595347ab9cc59bb8 Mon Sep 17 00:00:00 2001 From: Guillaume LADORME Date: Thu, 9 Jul 2026 10:36:33 +0200 Subject: [PATCH 1/3] [BUGFIX] pyroscope: fix timerange Signed-off-by: Guillaume LADORME --- pyroscope/src/utils/use-query.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/pyroscope/src/utils/use-query.ts b/pyroscope/src/utils/use-query.ts index 23f24d9fe..abeba74e9 100644 --- a/pyroscope/src/utils/use-query.ts +++ b/pyroscope/src/utils/use-query.ts @@ -26,12 +26,18 @@ import { getUnixTimeRange } from '../plugins'; export function useLabelNames(datasource: DatasourceSelector): UseQueryResult { const { data: client } = useDatasourceClient(datasource); + const { absoluteTimeRange } = useTimeRange(); + const { start, end } = getUnixTimeRange(absoluteTimeRange); return useQuery({ enabled: !!client, queryKey: ['searchLabelNames', 'datasource', datasource], queryFn: async () => { - return await client!.searchLabelNames({}, { 'content-type': 'application/json' }, {}); + return await client!.searchLabelNames( + {}, + { 'content-type': 'application/json' }, + { start: start * 1000, end: end * 1000 } + ); }, }); } @@ -51,7 +57,7 @@ export function useLabelValues( return await client!.searchLabelValues( {}, { 'content-type': 'application/json' }, - { name: labelName, from: start, until: end } + { name: labelName, start: start * 1000, end: end * 1000 } ); }, }); @@ -68,7 +74,11 @@ export function useProfileTypes( enabled: !!client, queryKey: ['searchProfileTypes', 'datasource', datasource], queryFn: async () => { - return await client!.searchProfileTypes({}, { 'content-type': 'application/json' }, { from: start, until: end }); + return await client!.searchProfileTypes( + {}, + { 'content-type': 'application/json' }, + { start: start * 1000, end: end * 1000 } + ); }, }); } @@ -82,7 +92,11 @@ export function useServices(datasource: DatasourceSelector): UseQueryResult { - return await client!.searchServices({}, { 'content-type': 'application/json' }, { from: start, until: end }); + return await client!.searchServices( + {}, + { 'content-type': 'application/json' }, + { start: start * 1000, end: end * 1000 } + ); }, }); } From 3f8258cff1153c669f1af8a8038089923945d2c0 Mon Sep 17 00:00:00 2001 From: Guillaume LADORME Date: Thu, 9 Jul 2026 12:25:47 +0200 Subject: [PATCH 2/3] Improve editor reactivity + info message if query is incomplete Signed-off-by: Guillaume LADORME --- pyroscope/src/components/Service.tsx | 1 - pyroscope/src/explore/PyroscopeExplorer.tsx | 24 ++++++++++++++----- pyroscope/src/model/profile-query-model.ts | 10 ++++++++ .../PyroscopeProfileQueryEditor.tsx | 11 ++++++--- .../get-profile-data.ts | 11 ++++++--- pyroscope/src/utils/use-query.ts | 8 +++---- 6 files changed, 48 insertions(+), 17 deletions(-) diff --git a/pyroscope/src/components/Service.tsx b/pyroscope/src/components/Service.tsx index 82937b814..73855fa5c 100644 --- a/pyroscope/src/components/Service.tsx +++ b/pyroscope/src/components/Service.tsx @@ -24,7 +24,6 @@ export interface ServiceProps { export function Service(props: ServiceProps): ReactElement { const { datasource, value, onChange } = props; - const { data: servicesOptions, isLoading: isServicesOptionsLoading } = useServices(datasource); function handleServiceChange(_event: SyntheticEvent, newValue: string | null): void { diff --git a/pyroscope/src/explore/PyroscopeExplorer.tsx b/pyroscope/src/explore/PyroscopeExplorer.tsx index 144eaf28b..88d90a194 100644 --- a/pyroscope/src/explore/PyroscopeExplorer.tsx +++ b/pyroscope/src/explore/PyroscopeExplorer.tsx @@ -11,12 +11,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Box, Stack } from '@mui/material'; +import { Alert, Box, Stack } from '@mui/material'; import { ReactElement, useState } from 'react'; import { DataQueriesProvider, MultiQueryEditor } from '@perses-dev/plugin-system'; import { Panel } from '@perses-dev/dashboards'; import { useExplorerManagerContext } from '@perses-dev/explore'; import { QueryDefinition } from '@perses-dev/spec'; +import { isProfileQueryComplete, PyroscopeProfileQuerySpec } from '../model'; interface ProfilesExplorerQueryParams { queries?: QueryDefinition[]; @@ -53,6 +54,11 @@ export function PyroscopeExplorer(): ReactElement { const [queryDefinitions, setQueryDefinitions] = useState(queries); + // A profile query is only executable once a service and a profile type are selected. + const hasIncompleteQuery = queries.some( + (query) => !isProfileQueryComplete((query.spec?.plugin?.spec ?? {}) as Partial) + ); + return ( setData({ queries: queryDefinitions })} /> - - - - - + {hasIncompleteQuery ? ( + + The query is not complete. Please select a service and a profile type to run the query. + + ) : ( + + + + + + )} ); } diff --git a/pyroscope/src/model/profile-query-model.ts b/pyroscope/src/model/profile-query-model.ts index 00b9101f7..a04ee5f9c 100644 --- a/pyroscope/src/model/profile-query-model.ts +++ b/pyroscope/src/model/profile-query-model.ts @@ -23,3 +23,13 @@ export interface PyroscopeProfileQuerySpec { filters?: LabelFilter[]; service?: string; } + +/** + * A Pyroscope profile query needs at least a service and a profile type to be executable. + * When either is missing, the query is considered incomplete and should not be run. + */ +export function isProfileQueryComplete( + spec: Partial> +): boolean { + return !!spec.service && !!spec.profileType; +} diff --git a/pyroscope/src/plugins/pyroscope-profile-query/PyroscopeProfileQueryEditor.tsx b/pyroscope/src/plugins/pyroscope-profile-query/PyroscopeProfileQueryEditor.tsx index 139b09561..5eb9da747 100644 --- a/pyroscope/src/plugins/pyroscope-profile-query/PyroscopeProfileQueryEditor.tsx +++ b/pyroscope/src/plugins/pyroscope-profile-query/PyroscopeProfileQueryEditor.tsx @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { DatasourceSelect, DatasourceSelectProps } from '@perses-dev/plugin-system'; +import { DatasourceSelect, DatasourceSelectProps, useDatasourceSelectValueToSelector } from '@perses-dev/plugin-system'; import { useId } from '@perses-dev/components'; import { produce } from 'immer'; import { FormControl, InputLabel, Stack, TextField, useTheme } from '@mui/material'; @@ -21,6 +21,7 @@ import { isDefaultPyroscopeSelector, isPyroscopeDatasourceSelector, PYROSCOPE_DATASOURCE_KIND, + PyroscopeDatasourceSelector, } from '../../model'; import { ProfileTypeSelector, Service, Filters } from '../../components'; import { @@ -34,7 +35,11 @@ import { export function PyroscopeProfileQueryEditor(props: ProfileQueryEditorProps): ReactElement { const { onChange, value } = props; const { datasource } = value; - const selectedDatasource = datasource ?? DEFAULT_PYROSCOPE; + const datasourceSelectValue = datasource ?? DEFAULT_PYROSCOPE; + const selectedDatasource = useDatasourceSelectValueToSelector( + datasourceSelectValue, + PYROSCOPE_DATASOURCE_KIND + ) as PyroscopeDatasourceSelector; const datasourceSelectLabelID = useId('pyroscope-datasource-label'); const { maxNodes, handleMaxNodesChange, maxNodesHasError } = useMaxNodesState(props); @@ -67,7 +72,7 @@ export function PyroscopeProfileQueryEditor(props: ProfileQueryEditorProps): Rea ({ enabled: !!client, - queryKey: ['searchLabelNames', 'datasource', datasource], + queryKey: ['searchLabelNames', client], queryFn: async () => { return await client!.searchLabelNames( {}, @@ -52,7 +52,7 @@ export function useLabelValues( return useQuery({ enabled: !!client, - queryKey: ['searchLabelValues', labelName, 'datasource', datasource], + queryKey: ['searchLabelValues', labelName, client], queryFn: async () => { return await client!.searchLabelValues( {}, @@ -72,7 +72,7 @@ export function useProfileTypes( return useQuery({ enabled: !!client, - queryKey: ['searchProfileTypes', 'datasource', datasource], + queryKey: ['searchProfileTypes', client], queryFn: async () => { return await client!.searchProfileTypes( {}, @@ -90,7 +90,7 @@ export function useServices(datasource: DatasourceSelector): UseQueryResult({ enabled: !!client, - queryKey: ['searchServices', 'datasource', datasource], + queryKey: ['searchServices', client], queryFn: async () => { return await client!.searchServices( {}, From 1138149fd2eb546a004bb3dc6700b3768dc1ca4d Mon Sep 17 00:00:00 2001 From: Guillaume LADORME Date: Wed, 15 Jul 2026 14:47:44 +0200 Subject: [PATCH 3/3] Apply feedbacks Signed-off-by: Guillaume LADORME --- pyroscope/schemas/pyroscope-profile-query/query.cue | 3 ++- pyroscope/src/components/Service.tsx | 3 +-- pyroscope/src/utils/use-query.ts | 11 +++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pyroscope/schemas/pyroscope-profile-query/query.cue b/pyroscope/schemas/pyroscope-profile-query/query.cue index 6696bd9b1..3decad72f 100644 --- a/pyroscope/schemas/pyroscope-profile-query/query.cue +++ b/pyroscope/schemas/pyroscope-profile-query/query.cue @@ -14,6 +14,7 @@ package model import ( + "strings" ds "github.com/perses/plugins/pyroscope/schemas/datasource:model" ) @@ -21,7 +22,7 @@ kind: "PyroscopeProfileQuery" spec: close({ ds.#selector maxNodes?: number - profileType: string + profileType: strings.MinRunes(1) filters?: [...{ labelName: string labelValue: string diff --git a/pyroscope/src/components/Service.tsx b/pyroscope/src/components/Service.tsx index 73855fa5c..4e731100e 100644 --- a/pyroscope/src/components/Service.tsx +++ b/pyroscope/src/components/Service.tsx @@ -22,8 +22,7 @@ export interface ServiceProps { onChange?(value: string): void; } -export function Service(props: ServiceProps): ReactElement { - const { datasource, value, onChange } = props; +export function Service({ datasource, value, onChange }: ServiceProps): ReactElement { const { data: servicesOptions, isLoading: isServicesOptionsLoading } = useServices(datasource); function handleServiceChange(_event: SyntheticEvent, newValue: string | null): void { diff --git a/pyroscope/src/utils/use-query.ts b/pyroscope/src/utils/use-query.ts index 4940d52f2..78296a74e 100644 --- a/pyroscope/src/utils/use-query.ts +++ b/pyroscope/src/utils/use-query.ts @@ -24,6 +24,9 @@ import { } from '../model'; import { getUnixTimeRange } from '../plugins'; +// Pyroscope need timestamp in milliseconds, but the time range from Perses is in seconds. +const MILLISECONDS = 1_000; + export function useLabelNames(datasource: DatasourceSelector): UseQueryResult { const { data: client } = useDatasourceClient(datasource); const { absoluteTimeRange } = useTimeRange(); @@ -36,7 +39,7 @@ export function useLabelNames(datasource: DatasourceSelector): UseQueryResult