tracesChart.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import {useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import {getInterval} from 'sentry/components/charts/utils';
  4. import {CHART_PALETTE} from 'sentry/constants/chartPalette';
  5. import {t} from 'sentry/locale';
  6. import type {Series} from 'sentry/types/echarts';
  7. import {tooltipFormatter} from 'sentry/utils/discover/charts';
  8. import {decodeList} from 'sentry/utils/queryString';
  9. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  10. import {useLocation} from 'sentry/utils/useLocation';
  11. import usePageFilters from 'sentry/utils/usePageFilters';
  12. import Chart, {ChartType} from 'sentry/views/insights/common/components/chart';
  13. import ChartPanel from 'sentry/views/insights/common/components/chartPanel';
  14. import {useSpanIndexedSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
  15. import {CHART_HEIGHT} from 'sentry/views/insights/database/settings';
  16. import {areQueriesEmpty} from './utils';
  17. interface Props {}
  18. export function TracesChart({}: Props) {
  19. const location = useLocation();
  20. const queries = useMemo(() => {
  21. return decodeList(location.query.query)?.map(query => query.trim());
  22. }, [location.query.query]);
  23. const enabled = useMemo(
  24. () => [
  25. true, // always visualize the first series
  26. Boolean(queries?.[1]),
  27. Boolean(queries?.[2]),
  28. ],
  29. [queries]
  30. );
  31. const firstCountSeries = useTraceCountSeries({
  32. query: queries?.[0] || '',
  33. enabled: enabled[0],
  34. });
  35. const secondCountSeries = useTraceCountSeries({
  36. query: queries?.[1],
  37. enabled: enabled[1],
  38. });
  39. const thirdCountSeries = useTraceCountSeries({
  40. query: queries?.[2],
  41. enabled: enabled[2],
  42. });
  43. const seriesAreLoading =
  44. // Disabled queries have `isLoading: true`, but this changes in v5.
  45. // To handle this gracefully, we check if the query is enabled + isLoading.
  46. //
  47. // References
  48. // - https://tanstack.com/query/v4/docs/framework/react/guides/disabling-queries
  49. // - https://tanstack.com/query/latest/docs/framework/react/guides/disabling-queries#isloading-previously-isinitialloading
  50. (enabled[0] && firstCountSeries.isLoading) ||
  51. (enabled[1] && secondCountSeries.isLoading) ||
  52. (enabled[2] && thirdCountSeries.isLoading);
  53. const error = useMemo(() => {
  54. const errors = [
  55. firstCountSeries.error,
  56. secondCountSeries.error,
  57. thirdCountSeries.error,
  58. ];
  59. for (let i = 0; i < errors.length; i++) {
  60. if (!enabled[i]) {
  61. continue;
  62. }
  63. if (errors[i]) {
  64. return errors[i];
  65. }
  66. }
  67. return null;
  68. }, [enabled, firstCountSeries, secondCountSeries, thirdCountSeries]);
  69. const chartData = useMemo<Series[]>(() => {
  70. const series = [firstCountSeries.data, secondCountSeries.data, thirdCountSeries.data];
  71. const allData: Series[] = [];
  72. for (let i = 0; i < series.length; i++) {
  73. if (!enabled[i] || error) {
  74. continue;
  75. }
  76. const data = series[i]['count()'];
  77. data.color = CHART_PALETTE[2][i];
  78. data.seriesName =
  79. `span ${i + 1}: ${queries[i] || t('All spans')}` || t('All spans');
  80. allData.push(data);
  81. }
  82. return allData;
  83. }, [
  84. enabled,
  85. queries,
  86. error,
  87. firstCountSeries.data,
  88. secondCountSeries.data,
  89. thirdCountSeries.data,
  90. ]);
  91. return (
  92. <ChartContainer>
  93. <ChartPanel
  94. title={areQueriesEmpty(queries) ? t('All Spans') : t('All Matching Spans')}
  95. >
  96. <Chart
  97. height={CHART_HEIGHT}
  98. grid={{
  99. left: '0',
  100. right: '0',
  101. top: '8px',
  102. bottom: '0',
  103. }}
  104. data={chartData}
  105. error={error}
  106. loading={seriesAreLoading}
  107. chartColors={CHART_PALETTE[2]}
  108. type={ChartType.LINE}
  109. aggregateOutputFormat="number"
  110. showLegend
  111. tooltipFormatterOptions={{
  112. valueFormatter: value => tooltipFormatter(value),
  113. }}
  114. />
  115. </ChartPanel>
  116. </ChartContainer>
  117. );
  118. }
  119. const ChartContainer = styled('div')`
  120. display: grid;
  121. gap: 0;
  122. grid-template-columns: 1fr;
  123. `;
  124. const useTraceCountSeries = ({
  125. enabled,
  126. query,
  127. }: {
  128. enabled: boolean;
  129. query: string | null;
  130. }) => {
  131. const pageFilters = usePageFilters();
  132. return useSpanIndexedSeries(
  133. {
  134. search: new MutableSearch(query ?? ''),
  135. yAxis: ['count()'],
  136. interval: getInterval(pageFilters.selection.datetime, 'metrics'),
  137. overriddenRoute: 'traces-stats',
  138. enabled,
  139. },
  140. 'api.trace-explorer.stats'
  141. );
  142. };