durationChart.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import type {ComponentProps} from 'react';
  2. import type {EChartHighlightHandler, Series} from 'sentry/types/echarts';
  3. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  4. import {AVG_COLOR} from 'sentry/views/insights/colors';
  5. import Chart, {ChartType} from 'sentry/views/insights/common/components/chart';
  6. import ChartPanel from 'sentry/views/insights/common/components/chartPanel';
  7. import {getDurationChartTitle} from 'sentry/views/insights/common/views/spans/types';
  8. import {ALERTS} from 'sentry/views/insights/http/alerts';
  9. import {CHART_HEIGHT} from 'sentry/views/insights/http/settings';
  10. import type {SpanMetricsQueryFilters} from 'sentry/views/insights/types';
  11. interface Props {
  12. isLoading: boolean;
  13. series: Series[];
  14. error?: Error | null;
  15. filters?: SpanMetricsQueryFilters;
  16. onHighlight?: (highlights: Highlight[], event: Event) => void; // TODO: Correctly type this
  17. scatterPlot?: ComponentProps<typeof Chart>['scatterPlot'];
  18. }
  19. interface Highlight {
  20. dataPoint: Series['data'][number];
  21. series: Series[];
  22. }
  23. export function DurationChart({
  24. series,
  25. scatterPlot,
  26. isLoading,
  27. error,
  28. onHighlight,
  29. filters,
  30. }: Props) {
  31. // TODO: This is duplicated from `DurationChart` in `SampleList`. Resolve the duplication
  32. const handleChartHighlight: EChartHighlightHandler = function (event) {
  33. // ignore mouse hovering over the chart legend
  34. if (!event.batch) {
  35. return;
  36. }
  37. // TODO: Gross hack. Even though `scatterPlot` is a separate prop, it's just an array of `Series` that gets appended to the main series. To find the point that was hovered, we re-construct the correct series order. It would have been cleaner to just pass the scatter plot as its own, single series
  38. const allSeries = [...series, ...(scatterPlot ?? [])];
  39. const highlightedDataPoints = event.batch.map(batch => {
  40. let {seriesIndex} = batch;
  41. const {dataIndex} = batch;
  42. // TODO: More hacks. The Chart component partitions the data series into a complete and incomplete series. Wrap the series index to work around overflowing index.
  43. seriesIndex = seriesIndex % allSeries.length;
  44. const highlightedSeries = allSeries?.[seriesIndex];
  45. const highlightedDataPoint = highlightedSeries?.data?.[dataIndex];
  46. return {series: highlightedSeries, dataPoint: highlightedDataPoint};
  47. });
  48. onHighlight?.(highlightedDataPoints, event);
  49. };
  50. const filterString = filters && MutableSearch.fromQueryObject(filters).formatString();
  51. const alertConfig = {
  52. ...ALERTS.duration,
  53. query: filterString ?? ALERTS.duration.query,
  54. };
  55. return (
  56. <ChartPanel title={getDurationChartTitle('http')} alertConfigs={[alertConfig]}>
  57. <Chart
  58. height={CHART_HEIGHT}
  59. grid={{
  60. left: '0',
  61. right: '0',
  62. top: '8px',
  63. bottom: '0',
  64. }}
  65. data={series}
  66. onHighlight={handleChartHighlight}
  67. scatterPlot={scatterPlot}
  68. loading={isLoading}
  69. error={error}
  70. chartColors={[AVG_COLOR]}
  71. type={ChartType.LINE}
  72. aggregateOutputFormat="duration"
  73. />
  74. </ChartPanel>
  75. );
  76. }