index.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import {Fragment, useCallback, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import {getInterval} from 'sentry/components/charts/utils';
  4. import {CompactSelect} from 'sentry/components/compactSelect';
  5. import {CHART_PALETTE} from 'sentry/constants/chartPalette';
  6. import {t} from 'sentry/locale';
  7. import {space} from 'sentry/styles/space';
  8. import {dedupeArray} from 'sentry/utils/dedupeArray';
  9. import {aggregateOutputType} from 'sentry/utils/discover/fields';
  10. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  11. import usePageFilters from 'sentry/utils/usePageFilters';
  12. import {formatVersion} from 'sentry/utils/versions/formatVersion';
  13. import {useChartInterval} from 'sentry/views/explore/hooks/useChartInterval';
  14. import {useDataset} from 'sentry/views/explore/hooks/useDataset';
  15. import {useVisualizes} from 'sentry/views/explore/hooks/useVisualizes';
  16. import Chart, {ChartType} from 'sentry/views/insights/common/components/chart';
  17. import ChartPanel from 'sentry/views/insights/common/components/chartPanel';
  18. import {useSortedTimeSeries} from 'sentry/views/insights/common/queries/useSortedTimeSeries';
  19. import {CHART_HEIGHT} from 'sentry/views/insights/database/settings';
  20. import {useGroupBys} from '../hooks/useGroupBys';
  21. import {useResultMode} from '../hooks/useResultsMode';
  22. import {useSorts} from '../hooks/useSorts';
  23. import {TOP_EVENTS_LIMIT, useTopEvents} from '../hooks/useTopEvents';
  24. import {formatSort} from '../tables/aggregatesTable';
  25. interface ExploreChartsProps {
  26. query: string;
  27. }
  28. const exploreChartTypeOptions = [
  29. {
  30. value: ChartType.LINE,
  31. label: t('Line'),
  32. },
  33. {
  34. value: ChartType.AREA,
  35. label: t('Area'),
  36. },
  37. {
  38. value: ChartType.BAR,
  39. label: t('Bar'),
  40. },
  41. ];
  42. // TODO: Update to support aggregate mode and multiple queries / visualizations
  43. export function ExploreCharts({query}: ExploreChartsProps) {
  44. const pageFilters = usePageFilters();
  45. const [dataset] = useDataset();
  46. const [visualizes, setVisualizes] = useVisualizes();
  47. const [interval, setInterval, intervalOptions] = useChartInterval();
  48. const {groupBys, isLoadingGroupBys} = useGroupBys();
  49. const [resultMode] = useResultMode();
  50. const topEvents = useTopEvents();
  51. const fields: string[] = useMemo(() => {
  52. if (resultMode === 'samples') {
  53. return [];
  54. }
  55. return [...groupBys, ...visualizes.flatMap(visualize => visualize.yAxes)].filter(
  56. Boolean
  57. );
  58. }, [resultMode, groupBys, visualizes]);
  59. const [sorts] = useSorts({fields});
  60. const orderby: string | string[] | undefined = useMemo(() => {
  61. if (!sorts.length) {
  62. return undefined;
  63. }
  64. return sorts.map(formatSort);
  65. }, [sorts]);
  66. const yAxes = useMemo(() => {
  67. const deduped = dedupeArray(visualizes.flatMap(visualize => visualize.yAxes));
  68. deduped.sort();
  69. return deduped;
  70. }, [visualizes]);
  71. const timeSeriesResult = useSortedTimeSeries(
  72. {
  73. search: new MutableSearch(query ?? ''),
  74. yAxis: yAxes,
  75. interval: interval ?? getInterval(pageFilters.selection.datetime, 'metrics'),
  76. enabled: !isLoadingGroupBys,
  77. fields,
  78. orderby,
  79. topEvents,
  80. },
  81. 'api.explorer.stats',
  82. dataset
  83. );
  84. const getSeries = useCallback(
  85. (dedupedYAxes: string[]) => {
  86. return dedupedYAxes.flatMap(yAxis => {
  87. const series = timeSeriesResult.data[yAxis];
  88. return series !== undefined ? series : [];
  89. });
  90. },
  91. [timeSeriesResult]
  92. );
  93. const handleChartTypeChange = useCallback(
  94. (chartType: ChartType, index: number) => {
  95. const newVisualizes = visualizes.slice();
  96. newVisualizes[index] = {...newVisualizes[index], chartType};
  97. setVisualizes(newVisualizes);
  98. },
  99. [visualizes, setVisualizes]
  100. );
  101. return (
  102. <Fragment>
  103. {visualizes.map((visualize, index) => {
  104. const dedupedYAxes = dedupeArray(visualize.yAxes);
  105. const {chartType} = visualize;
  106. return (
  107. <ChartContainer key={index}>
  108. <ChartPanel>
  109. <ChartHeader>
  110. <ChartTitle>{dedupedYAxes.join(',')}</ChartTitle>
  111. <ChartSettingsContainer>
  112. <CompactSelect
  113. size="xs"
  114. triggerProps={{prefix: t('Type')}}
  115. value={chartType}
  116. options={exploreChartTypeOptions}
  117. onChange={option => handleChartTypeChange(option.value, index)}
  118. />
  119. <CompactSelect
  120. size="xs"
  121. value={interval}
  122. onChange={({value}) => setInterval(value)}
  123. triggerProps={{
  124. prefix: t('Interval'),
  125. }}
  126. options={intervalOptions}
  127. />
  128. </ChartSettingsContainer>
  129. </ChartHeader>
  130. <Chart
  131. height={CHART_HEIGHT}
  132. grid={{
  133. left: '0',
  134. right: '0',
  135. top: '8px',
  136. bottom: '0',
  137. }}
  138. legendFormatter={value => formatVersion(value)}
  139. data={getSeries(dedupedYAxes)}
  140. error={timeSeriesResult.error}
  141. loading={timeSeriesResult.isPending}
  142. // TODO Abdullah: Make chart colors dynamic, with changing topN events count and overlay count.
  143. chartColors={CHART_PALETTE[TOP_EVENTS_LIMIT - 1]}
  144. type={chartType}
  145. // for now, use the first y axis unit
  146. aggregateOutputFormat={aggregateOutputType(dedupedYAxes[0])}
  147. showLegend
  148. />
  149. </ChartPanel>
  150. </ChartContainer>
  151. );
  152. })}
  153. </Fragment>
  154. );
  155. }
  156. const ChartContainer = styled('div')`
  157. display: grid;
  158. gap: 0;
  159. grid-template-columns: 1fr;
  160. margin-bottom: ${space(2)};
  161. `;
  162. const ChartHeader = styled('div')`
  163. display: flex;
  164. align-items: flex-start;
  165. justify-content: space-between;
  166. margin-bottom: ${space(1)};
  167. `;
  168. const ChartTitle = styled('div')`
  169. ${p => p.theme.text.cardTitle}
  170. `;
  171. const ChartSettingsContainer = styled('div')`
  172. display: flex;
  173. align-items: center;
  174. gap: ${space(0.5)};
  175. `;