startDurationWidget.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import {getInterval} from 'sentry/components/charts/utils';
  2. import {t} from 'sentry/locale';
  3. import {space} from 'sentry/styles/space';
  4. import type {Series, SeriesDataUnit} from 'sentry/types/echarts';
  5. import type {MultiSeriesEventsStats} from 'sentry/types/organization';
  6. import {defined} from 'sentry/utils';
  7. import {tooltipFormatterUsingAggregateOutputType} from 'sentry/utils/discover/charts';
  8. import EventView from 'sentry/utils/discover/eventView';
  9. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  10. import {decodeScalar} from 'sentry/utils/queryString';
  11. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  12. import {useLocation} from 'sentry/utils/useLocation';
  13. import usePageFilters from 'sentry/utils/usePageFilters';
  14. import {formatVersion} from 'sentry/utils/versions/formatVersion';
  15. import {
  16. PRIMARY_RELEASE_COLOR,
  17. SECONDARY_RELEASE_COLOR,
  18. } from 'sentry/views/insights/colors';
  19. import Chart, {ChartType} from 'sentry/views/insights/common/components/chart';
  20. import MiniChartPanel from 'sentry/views/insights/common/components/miniChartPanel';
  21. import {useReleaseSelection} from 'sentry/views/insights/common/queries/useReleases';
  22. import {formatVersionAndCenterTruncate} from 'sentry/views/insights/common/utils/centerTruncate';
  23. import {STARFISH_CHART_INTERVAL_FIDELITY} from 'sentry/views/insights/common/utils/constants';
  24. import {appendReleaseFilters} from 'sentry/views/insights/common/utils/releaseComparison';
  25. import {useEventsStatsQuery} from 'sentry/views/insights/common/utils/useEventsStatsQuery';
  26. import {COLD_START_TYPE} from 'sentry/views/insights/mobile/appStarts/components/startTypeSelector';
  27. import useCrossPlatformProject from 'sentry/views/insights/mobile/common/queries/useCrossPlatformProject';
  28. import {SpanMetricsField} from 'sentry/views/insights/types';
  29. const COLD_START_CONDITIONS = ['span.op:app.start.cold', 'span.description:"Cold Start"'];
  30. const WARM_START_CONDITIONS = ['span.op:app.start.warm', 'span.description:"Warm Start"'];
  31. export function transformData(data?: MultiSeriesEventsStats, primaryRelease?: string) {
  32. const transformedSeries: {[releaseName: string]: Series} = {};
  33. if (defined(data)) {
  34. Object.keys(data).forEach(releaseName => {
  35. transformedSeries[releaseName] = {
  36. seriesName: releaseName,
  37. data:
  38. data[releaseName]?.data?.map(datum => {
  39. return {
  40. name: datum[0] * 1000,
  41. value: datum[1][0].count,
  42. } as SeriesDataUnit;
  43. }) ?? [],
  44. ...(primaryRelease === releaseName
  45. ? {color: PRIMARY_RELEASE_COLOR}
  46. : {
  47. color: SECONDARY_RELEASE_COLOR,
  48. lineStyle: {type: 'dashed'},
  49. }),
  50. };
  51. });
  52. }
  53. return transformedSeries;
  54. }
  55. interface Props {
  56. chartHeight: number;
  57. additionalFilters?: string[];
  58. }
  59. function StartDurationWidget({additionalFilters, chartHeight}: Props) {
  60. const pageFilter = usePageFilters();
  61. const location = useLocation();
  62. const {
  63. primaryRelease,
  64. secondaryRelease,
  65. isLoading: isReleasesLoading,
  66. } = useReleaseSelection();
  67. const {isProjectCrossPlatform, selectedPlatform} = useCrossPlatformProject();
  68. const startType =
  69. decodeScalar(location.query[SpanMetricsField.APP_START_TYPE]) ?? COLD_START_TYPE;
  70. const query = new MutableSearch([
  71. ...(startType === COLD_START_TYPE ? COLD_START_CONDITIONS : WARM_START_CONDITIONS),
  72. ...(additionalFilters ?? []),
  73. ]);
  74. if (isProjectCrossPlatform) {
  75. query.addFilterValue('os.name', selectedPlatform);
  76. }
  77. const queryString = appendReleaseFilters(query, primaryRelease, secondaryRelease);
  78. const {
  79. data: series,
  80. isLoading: isSeriesLoading,
  81. error: seriesError,
  82. } = useEventsStatsQuery({
  83. eventView: EventView.fromNewQueryWithPageFilters(
  84. {
  85. name: '',
  86. topEvents: '2',
  87. fields: ['release', 'avg(span.duration)'],
  88. yAxis: ['avg(span.duration)'],
  89. query: queryString,
  90. dataset: DiscoverDatasets.SPANS_METRICS,
  91. version: 2,
  92. interval: getInterval(
  93. pageFilter.selection.datetime,
  94. STARFISH_CHART_INTERVAL_FIDELITY
  95. ),
  96. },
  97. pageFilter.selection
  98. ),
  99. enabled: !isReleasesLoading,
  100. referrer: 'api.starfish.mobile-startup-series',
  101. initialData: {},
  102. });
  103. // The expected response is a multi series response, but if there is no data
  104. // then we get an object representing a single series with all empty values
  105. // (i.e without being grouped by release)
  106. const hasReleaseData = series && !('data' in series);
  107. // Only transform the data is we know there's at least one release
  108. const transformedSeries = hasReleaseData
  109. ? Object.values(transformData(series, primaryRelease)).sort((releaseA, _releaseB) =>
  110. releaseA.seriesName === primaryRelease ? -1 : 1
  111. )
  112. : [];
  113. return (
  114. <MiniChartPanel
  115. title={
  116. startType === COLD_START_TYPE ? t('Average Cold Start') : t('Average Warm Start')
  117. }
  118. subtitle={
  119. primaryRelease
  120. ? t(
  121. '%s v. %s',
  122. formatVersionAndCenterTruncate(primaryRelease, 12),
  123. secondaryRelease ? formatVersionAndCenterTruncate(secondaryRelease, 12) : ''
  124. )
  125. : ''
  126. }
  127. >
  128. <Chart
  129. data={transformedSeries}
  130. height={chartHeight}
  131. loading={isSeriesLoading}
  132. grid={{
  133. left: '0',
  134. right: '0',
  135. top: space(2),
  136. bottom: '0',
  137. }}
  138. showLegend
  139. definedAxisTicks={2}
  140. type={ChartType.LINE}
  141. aggregateOutputFormat="duration"
  142. tooltipFormatterOptions={{
  143. valueFormatter: value =>
  144. tooltipFormatterUsingAggregateOutputType(value, 'duration'),
  145. nameFormatter: value => formatVersion(value),
  146. }}
  147. legendFormatter={value => formatVersion(value)}
  148. error={seriesError}
  149. />
  150. </MiniChartPanel>
  151. );
  152. }
  153. export default StartDurationWidget;