useVisualizes.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import {useCallback, useMemo} from 'react';
  2. import type {Location} from 'history';
  3. import {defined} from 'sentry/utils';
  4. import {parseFunction} from 'sentry/utils/discover/fields';
  5. import {
  6. ALLOWED_EXPLORE_VISUALIZE_AGGREGATES,
  7. ALLOWED_EXPLORE_VISUALIZE_FIELDS,
  8. } from 'sentry/utils/fields';
  9. import {decodeList} from 'sentry/utils/queryString';
  10. import {useLocation} from 'sentry/utils/useLocation';
  11. import {useNavigate} from 'sentry/utils/useNavigate';
  12. import {ChartType} from 'sentry/views/insights/common/components/chart';
  13. export const MAX_VISUALIZES = 4;
  14. type BaseVisualize = {
  15. chartType: ChartType;
  16. yAxes: string[];
  17. };
  18. export type Visualize = BaseVisualize & {
  19. label: string;
  20. };
  21. interface Options {
  22. location: Location;
  23. navigate: ReturnType<typeof useNavigate>;
  24. }
  25. export const DEFAULT_VISUALIZATION = `${ALLOWED_EXPLORE_VISUALIZE_AGGREGATES[0]}(${ALLOWED_EXPLORE_VISUALIZE_FIELDS[0]})`;
  26. export function useVisualizes(): [Visualize[], (visualizes: BaseVisualize[]) => void] {
  27. const location = useLocation();
  28. const navigate = useNavigate();
  29. const options = {location, navigate};
  30. return useVisualizesImpl(options);
  31. }
  32. function useVisualizesImpl({
  33. location,
  34. navigate,
  35. }: Options): [Visualize[], (visualizes: BaseVisualize[]) => void] {
  36. const visualizes: Visualize[] = useMemo(() => {
  37. const rawVisualizes = decodeList(location.query.visualize);
  38. const result: Visualize[] = rawVisualizes
  39. .map(parseVisualizes)
  40. .filter(defined)
  41. .filter(parsed => parsed.yAxes.length > 0)
  42. .map((parsed, i) => {
  43. return {
  44. chartType: parsed.chartType,
  45. yAxes: parsed.yAxes,
  46. label: String.fromCharCode(65 + i), // starts from 'A'
  47. };
  48. });
  49. return result.length
  50. ? result
  51. : [{chartType: ChartType.LINE, label: 'A', yAxes: [DEFAULT_VISUALIZATION]}];
  52. }, [location.query.visualize]);
  53. const setVisualizes = useCallback(
  54. (newVisualizes: BaseVisualize[]) => {
  55. const stringified: string[] = [];
  56. for (const visualize of newVisualizes) {
  57. // ignore the label from visualize because it'll determined later
  58. stringified.push(
  59. JSON.stringify({
  60. chartType: visualize.chartType,
  61. yAxes: visualize.yAxes,
  62. })
  63. );
  64. }
  65. navigate({
  66. ...location,
  67. query: {
  68. ...location.query,
  69. visualize: stringified,
  70. },
  71. });
  72. },
  73. [location, navigate]
  74. );
  75. return [visualizes, setVisualizes];
  76. }
  77. function parseVisualizes(raw: string): BaseVisualize | null {
  78. try {
  79. const parsed = JSON.parse(raw);
  80. if (!defined(parsed) || !Array.isArray(parsed.yAxes)) {
  81. return null;
  82. }
  83. const yAxes = parsed.yAxes.filter(parseFunction);
  84. if (yAxes.length <= 0) {
  85. return null;
  86. }
  87. let chartType = Number(parsed.chartType);
  88. if (isNaN(chartType) || !Object.values(ChartType).includes(chartType)) {
  89. chartType = ChartType.LINE;
  90. }
  91. return {yAxes, chartType};
  92. } catch (error) {
  93. return null;
  94. }
  95. }