diff --git a/static/app/views/explore/hooks/useVisualize.spec.tsx b/static/app/views/explore/hooks/useVisualize.spec.tsx new file mode 100644 index 00000000000000..58a40c906b713f --- /dev/null +++ b/static/app/views/explore/hooks/useVisualize.spec.tsx @@ -0,0 +1,42 @@ +import {createMemoryHistory, Route, Router, RouterContext} from 'react-router'; + +import {act, render} from 'sentry-test/reactTestingLibrary'; + +import {useVisualize} from 'sentry/views/explore/hooks/useVisualize'; +import {RouteContext} from 'sentry/views/routeContext'; + +describe('useVisualize', function () { + it('allows changing results mode', function () { + let visualize, setVisualize; + + function TestPage() { + [visualize, setVisualize] = useVisualize(); + return null; + } + + const memoryHistory = createMemoryHistory(); + + render( + { + return ( + + + + ); + }} + > + + + ); + + expect(visualize).toEqual('count(span.duration)'); // default + + act(() => setVisualize('p75(span.duration)')); + expect(visualize).toEqual('p75(span.duration)'); + + act(() => setVisualize('count(span.duration)')); + expect(visualize).toEqual('count(span.duration)'); + }); +}); diff --git a/static/app/views/explore/hooks/useVisualize.tsx b/static/app/views/explore/hooks/useVisualize.tsx new file mode 100644 index 00000000000000..9f015b9bfcdb73 --- /dev/null +++ b/static/app/views/explore/hooks/useVisualize.tsx @@ -0,0 +1,54 @@ +import {useCallback, useMemo} from 'react'; +import type {Location} from 'history'; + +import {AggregationKey} from 'sentry/utils/fields'; +import {decodeScalar} from 'sentry/utils/queryString'; +import {useLocation} from 'sentry/utils/useLocation'; +import {useNavigate} from 'sentry/utils/useNavigate'; +import {SpanIndexedField} from 'sentry/views/insights/types'; + +interface Options { + location: Location; + navigate: ReturnType; +} + +// TODO: Extend the two lists below with more options upon backend support +export const ALLOWED_VISUALIZE_FIELDS: SpanIndexedField[] = [ + SpanIndexedField.SPAN_DURATION, +]; + +export const ALLOWED_VISUALIZE_AGGREGATES: AggregationKey[] = [AggregationKey.COUNT]; + +export const DEFAULT_VISUALIZATION = `${ALLOWED_VISUALIZE_AGGREGATES[0]}(${ALLOWED_VISUALIZE_FIELDS[0]})`; + +export function useVisualize(): [string, (visualize: string) => void] { + const location = useLocation(); + const navigate = useNavigate(); + const options = {location, navigate}; + + return useVisualizeImpl(options); +} + +function useVisualizeImpl({ + location, + navigate, +}: Options): [string, (visualize: string) => void] { + const visualize: string | undefined = useMemo(() => { + return decodeScalar(location.query.visualize) ?? DEFAULT_VISUALIZATION; + }, [location.query.visualize]); + + const setVisualize = useCallback( + (newVisualize: string) => { + navigate({ + ...location, + query: { + ...location.query, + visualize: newVisualize, + }, + }); + }, + [location, navigate] + ); + + return [visualize, setVisualize]; +} diff --git a/static/app/views/explore/toolbar/index.tsx b/static/app/views/explore/toolbar/index.tsx index f05d027856f271..008973aaa7fd81 100644 --- a/static/app/views/explore/toolbar/index.tsx +++ b/static/app/views/explore/toolbar/index.tsx @@ -9,6 +9,8 @@ import {ToolbarResults} from 'sentry/views/explore/toolbar/toolbarResults'; import {ToolbarSortBy} from 'sentry/views/explore/toolbar/toolbarSortBy'; import {ToolbarVisualize} from 'sentry/views/explore/toolbar/toolbarVisualize'; +import {useVisualize} from '../hooks/useVisualize'; + type Extras = 'dataset toggle'; interface ExploreToolbarProps { @@ -20,6 +22,7 @@ export function ExploreToolbar({extras}: ExploreToolbarProps) { const [resultMode, setResultMode] = useResultMode(); const [sampleFields] = useSampleFields(); const [sorts, setSorts] = useSorts({fields: sampleFields}); + const [visualize, setVisualize] = useVisualize(); return (
@@ -27,7 +30,7 @@ export function ExploreToolbar({extras}: ExploreToolbarProps) { )} - + diff --git a/static/app/views/explore/toolbar/toolbarVisualize.tsx b/static/app/views/explore/toolbar/toolbarVisualize.tsx index db43be15b1d1af..938174f8872a68 100644 --- a/static/app/views/explore/toolbar/toolbarVisualize.tsx +++ b/static/app/views/explore/toolbar/toolbarVisualize.tsx @@ -1,13 +1,71 @@ +import {useMemo} from 'react'; +import styled from '@emotion/styled'; + +import {CompactSelect, type SelectOption} from 'sentry/components/compactSelect'; import {t} from 'sentry/locale'; +import {parseFunction} from 'sentry/utils/discover/fields'; +import type {SpanIndexedField} from 'sentry/views/insights/types'; + +import { + ALLOWED_VISUALIZE_AGGREGATES, + ALLOWED_VISUALIZE_FIELDS, +} from '../hooks/useVisualize'; import {ToolbarHeading, ToolbarSection} from './styles'; -interface ToolbarVisualizeProps {} +interface ToolbarVisualizeProps { + setVisualize: (visualize: string) => void; + visualize: string; +} + +export function ToolbarVisualize({visualize, setVisualize}: ToolbarVisualizeProps) { + const parsedVisualize = useMemo(() => { + return parseFunction(visualize); + }, [visualize]); + + const fieldOptions: SelectOption[] = ALLOWED_VISUALIZE_FIELDS.map( + field => { + return { + label: field, + value: field, + }; + } + ); + + const aggregateOptions: SelectOption[] = ALLOWED_VISUALIZE_AGGREGATES.map( + aggregate => { + return { + label: aggregate, + value: aggregate, + }; + } + ); -export function ToolbarVisualize({}: ToolbarVisualizeProps) { return ( - + {t('Visualize')} + + + setVisualize(`${parsedVisualize?.name}(${newField.value})`) + } + /> + + setVisualize(`${newAggregate.value}(${parsedVisualize?.arguments[0]})`) + } + /> + ); } + +const ToolbarContent = styled('div')` + display: flex; +`;