Browse Source

feat(explore): Adding visualize to explore toolbar. (#76631)

![Screenshot 2024-08-27 at 4 05
06 PM](https://github.com/user-attachments/assets/ccc5040f-e603-4761-aeb7-c9b7069eed70)

---------

Co-authored-by: Abdullah Khan <abdullahkhan@PG9Y57YDXQ.local>
Abdullah Khan 6 months ago
parent
commit
8fe12c859c

+ 42 - 0
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(
+      <Router
+        history={memoryHistory}
+        render={props => {
+          return (
+            <RouteContext.Provider value={props}>
+              <RouterContext {...props} />
+            </RouteContext.Provider>
+          );
+        }}
+      >
+        <Route path="/" component={TestPage} />
+      </Router>
+    );
+
+    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)');
+  });
+});

+ 54 - 0
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<typeof useNavigate>;
+}
+
+// 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];
+}

+ 4 - 1
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 (
     <div>
@@ -27,7 +30,7 @@ export function ExploreToolbar({extras}: ExploreToolbarProps) {
         <ToolbarDataset dataset={dataset} setDataset={setDataset} />
       )}
       <ToolbarResults resultMode={resultMode} setResultMode={setResultMode} />
-      <ToolbarVisualize />
+      <ToolbarVisualize visualize={visualize} setVisualize={setVisualize} />
       <ToolbarSortBy fields={sampleFields} sorts={sorts} setSorts={setSorts} />
       <ToolbarLimitTo />
       <ToolbarGroupBy disabled />

+ 61 - 3
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<SpanIndexedField>[] = ALLOWED_VISUALIZE_FIELDS.map(
+    field => {
+      return {
+        label: field,
+        value: field,
+      };
+    }
+  );
+
+  const aggregateOptions: SelectOption<string>[] = ALLOWED_VISUALIZE_AGGREGATES.map(
+    aggregate => {
+      return {
+        label: aggregate,
+        value: aggregate,
+      };
+    }
+  );
 
-export function ToolbarVisualize({}: ToolbarVisualizeProps) {
   return (
-    <ToolbarSection>
+    <ToolbarSection data-test-id="section-visualize">
       <ToolbarHeading>{t('Visualize')}</ToolbarHeading>
+      <ToolbarContent>
+        <CompactSelect
+          size="md"
+          options={fieldOptions}
+          value={parsedVisualize?.arguments[0]}
+          onChange={newField =>
+            setVisualize(`${parsedVisualize?.name}(${newField.value})`)
+          }
+        />
+        <CompactSelect
+          size="md"
+          options={aggregateOptions}
+          value={parsedVisualize?.name}
+          onChange={newAggregate =>
+            setVisualize(`${newAggregate.value}(${parsedVisualize?.arguments[0]})`)
+          }
+        />
+      </ToolbarContent>
     </ToolbarSection>
   );
 }
+
+const ToolbarContent = styled('div')`
+  display: flex;
+`;