tracesChart.tsx 4.4 KB

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