import {useCallback, useMemo} from 'react'; import type {Location} from 'history'; import {defined} from 'sentry/utils'; import {parseFunction} from 'sentry/utils/discover/fields'; import { ALLOWED_EXPLORE_VISUALIZE_AGGREGATES, ALLOWED_EXPLORE_VISUALIZE_FIELDS, } from 'sentry/utils/fields'; import {decodeList} from 'sentry/utils/queryString'; import {useLocation} from 'sentry/utils/useLocation'; import {useNavigate} from 'sentry/utils/useNavigate'; import {ChartType} from 'sentry/views/insights/common/components/chart'; type BaseVisualize = { chartType: ChartType; yAxes: string[]; }; export type Visualize = BaseVisualize & { label: string; }; interface Options { location: Location; navigate: ReturnType; } export const DEFAULT_VISUALIZATION = `${ALLOWED_EXPLORE_VISUALIZE_AGGREGATES[0]}(${ALLOWED_EXPLORE_VISUALIZE_FIELDS[0]})`; export function useVisualizes(): [Visualize[], (visualizes: BaseVisualize[]) => void] { const location = useLocation(); const navigate = useNavigate(); const options = {location, navigate}; return useVisualizesImpl(options); } function useVisualizesImpl({ location, navigate, }: Options): [Visualize[], (visualizes: BaseVisualize[]) => void] { const visualizes: Visualize[] = useMemo(() => { const rawVisualizes = decodeList(location.query.visualize); const result: Visualize[] = rawVisualizes .map(parseVisualizes) .filter(defined) .filter(parsed => parsed.yAxes.length > 0) .map((parsed, i) => { return { chartType: parsed.chartType, yAxes: parsed.yAxes, label: String.fromCharCode(65 + i), // starts from 'A' }; }); return result.length ? result : [{chartType: ChartType.LINE, label: 'A', yAxes: [DEFAULT_VISUALIZATION]}]; }, [location.query.visualize]); const setVisualizes = useCallback( (newVisualizes: BaseVisualize[]) => { const stringified: string[] = []; for (const visualize of newVisualizes) { // ignore the label from visualize because it'll determined later stringified.push( JSON.stringify({ chartType: visualize.chartType, yAxes: visualize.yAxes, }) ); } navigate({ ...location, query: { ...location.query, visualize: stringified, }, }); }, [location, navigate] ); return [visualizes, setVisualizes]; } function parseVisualizes(raw: string): BaseVisualize | null { try { const parsed = JSON.parse(raw); if (!defined(parsed) || !Array.isArray(parsed.yAxes)) { return null; } const yAxes = parsed.yAxes.filter(parseFunction); if (yAxes.length <= 0) { return null; } let chartType = Number(parsed.chartType); if (isNaN(chartType) || !Object.values(ChartType).includes(chartType)) { chartType = ChartType.LINE; } return {yAxes, chartType}; } catch (error) { return null; } }