deviceClassBreakdownBarChart.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import {BarChart} from 'sentry/components/charts/barChart';
  2. import ErrorPanel from 'sentry/components/charts/errorPanel';
  3. import TransitionChart from 'sentry/components/charts/transitionChart';
  4. import {getInterval} from 'sentry/components/charts/utils';
  5. import {IconWarning} from 'sentry/icons';
  6. import {t} from 'sentry/locale';
  7. import {space} from 'sentry/styles/space';
  8. import {
  9. axisLabelFormatter,
  10. getDurationUnit,
  11. tooltipFormatter,
  12. } from 'sentry/utils/discover/charts';
  13. import EventView from 'sentry/utils/discover/eventView';
  14. import {aggregateOutputType} from 'sentry/utils/discover/fields';
  15. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  16. import {decodeScalar} from 'sentry/utils/queryString';
  17. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  18. import {useLocation} from 'sentry/utils/useLocation';
  19. import usePageFilters from 'sentry/utils/usePageFilters';
  20. import {formatVersion} from 'sentry/utils/versions/formatVersion';
  21. import {
  22. PRIMARY_RELEASE_COLOR,
  23. SECONDARY_RELEASE_COLOR,
  24. } from 'sentry/views/insights/colors';
  25. import {LoadingScreen} from 'sentry/views/insights/common/components/chart';
  26. import MiniChartPanel from 'sentry/views/insights/common/components/miniChartPanel';
  27. import {useReleaseSelection} from 'sentry/views/insights/common/queries/useReleases';
  28. import {formatVersionAndCenterTruncate} from 'sentry/views/insights/common/utils/centerTruncate';
  29. import {STARFISH_CHART_INTERVAL_FIDELITY} from 'sentry/views/insights/common/utils/constants';
  30. import {appendReleaseFilters} from 'sentry/views/insights/common/utils/releaseComparison';
  31. import {COLD_START_TYPE} from 'sentry/views/insights/mobile/appStarts/components/startTypeSelector';
  32. import useCrossPlatformProject from 'sentry/views/insights/mobile/common/queries/useCrossPlatformProject';
  33. import {useTableQuery} from 'sentry/views/insights/mobile/screenload/components/tables/screensTable';
  34. import {YAxis, YAXIS_COLUMNS} from 'sentry/views/insights/mobile/screenload/constants';
  35. import {transformDeviceClassEvents} from 'sentry/views/insights/mobile/screenload/utils';
  36. import {SpanMetricsField} from 'sentry/views/insights/types';
  37. import {prepareQueryForLandingPage} from 'sentry/views/performance/data';
  38. const YAXES = [YAxis.COLD_START, YAxis.WARM_START];
  39. const XAXIS_CATEGORIES = ['high', 'medium', 'low', 'Unknown'];
  40. interface DeviceClassBreakdownBarChartProps {
  41. additionalFilters?: string[];
  42. chartHeight?: number;
  43. }
  44. function DeviceClassBreakdownBarChart({
  45. chartHeight,
  46. additionalFilters,
  47. }: DeviceClassBreakdownBarChartProps) {
  48. const pageFilter = usePageFilters();
  49. const location = useLocation();
  50. const {query: locationQuery} = location;
  51. const {
  52. primaryRelease,
  53. secondaryRelease,
  54. isLoading: isReleasesLoading,
  55. } = useReleaseSelection();
  56. const {isProjectCrossPlatform, selectedPlatform} = useCrossPlatformProject();
  57. const startType =
  58. decodeScalar(location.query[SpanMetricsField.APP_START_TYPE]) ?? COLD_START_TYPE;
  59. const yAxis =
  60. YAXIS_COLUMNS[startType === COLD_START_TYPE ? YAxis.COLD_START : YAxis.WARM_START];
  61. const query = new MutableSearch([...(additionalFilters ?? [])]);
  62. if (isProjectCrossPlatform) {
  63. query.addFilterValue('os.name', selectedPlatform);
  64. }
  65. const searchQuery = decodeScalar(locationQuery.query, '');
  66. if (searchQuery) {
  67. query.addStringFilter(prepareQueryForLandingPage(searchQuery, false));
  68. }
  69. const queryString = appendReleaseFilters(query, primaryRelease, secondaryRelease);
  70. const {
  71. data: startupDataByDeviceClass,
  72. isLoading,
  73. isError,
  74. } = useTableQuery({
  75. eventView: EventView.fromNewQueryWithPageFilters(
  76. {
  77. name: '',
  78. fields: [
  79. startType === COLD_START_TYPE
  80. ? 'avg(measurements.app_start_cold)'
  81. : 'avg(measurements.app_start_warm)',
  82. 'device.class',
  83. 'release',
  84. ],
  85. yAxis: YAXES.map(val => YAXIS_COLUMNS[val]),
  86. query: queryString,
  87. dataset: DiscoverDatasets.METRICS,
  88. version: 2,
  89. interval: getInterval(
  90. pageFilter.selection.datetime,
  91. STARFISH_CHART_INTERVAL_FIDELITY
  92. ),
  93. },
  94. pageFilter.selection
  95. ),
  96. enabled: !isReleasesLoading,
  97. referrer: 'api.starfish.mobile-startup-bar-chart',
  98. initialData: {data: []},
  99. });
  100. const transformedData = transformDeviceClassEvents({
  101. data: startupDataByDeviceClass,
  102. yAxes: YAXES,
  103. primaryRelease,
  104. secondaryRelease,
  105. });
  106. const data = Object.values(
  107. transformedData[
  108. YAXIS_COLUMNS[startType === COLD_START_TYPE ? YAxis.COLD_START : YAxis.WARM_START]
  109. ]
  110. );
  111. return (
  112. <MiniChartPanel
  113. title={
  114. startType === COLD_START_TYPE
  115. ? t('Cold Start Device Distribution')
  116. : t('Warm Start Device Distribution')
  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. <TransitionChart
  129. loading={isLoading || isReleasesLoading}
  130. reloading={isLoading || isReleasesLoading}
  131. height={`${chartHeight}px`}
  132. >
  133. <LoadingScreen loading={Boolean(isLoading)} />
  134. {isError ? (
  135. <ErrorPanel height={`${chartHeight}px`}>
  136. <IconWarning color="gray300" size="lg" />
  137. </ErrorPanel>
  138. ) : (
  139. <BarChart
  140. legend={{
  141. show: true,
  142. right: 12,
  143. }}
  144. height={chartHeight}
  145. colors={[PRIMARY_RELEASE_COLOR, SECONDARY_RELEASE_COLOR]}
  146. series={
  147. data.map(series => ({
  148. ...series,
  149. data: series.data.map(datum =>
  150. datum.value !== 0
  151. ? {
  152. ...datum,
  153. itemStyle: {
  154. color:
  155. series.seriesName === primaryRelease
  156. ? PRIMARY_RELEASE_COLOR
  157. : SECONDARY_RELEASE_COLOR,
  158. },
  159. }
  160. : datum
  161. ),
  162. name: formatVersion(series.seriesName),
  163. })) ?? []
  164. }
  165. grid={{
  166. left: '0',
  167. right: '0',
  168. top: space(2),
  169. bottom: '0',
  170. containLabel: true,
  171. }}
  172. xAxis={{
  173. type: 'category',
  174. axisTick: {show: true},
  175. data: XAXIS_CATEGORIES,
  176. truncate: 14,
  177. axisLabel: {
  178. interval: 0,
  179. },
  180. }}
  181. yAxis={{
  182. axisLabel: {
  183. formatter(value: number) {
  184. return axisLabelFormatter(
  185. value,
  186. aggregateOutputType(yAxis),
  187. undefined,
  188. getDurationUnit(data ?? [])
  189. );
  190. },
  191. },
  192. }}
  193. tooltip={{
  194. valueFormatter: (value, _seriesName) => {
  195. return tooltipFormatter(value, aggregateOutputType(yAxis));
  196. },
  197. }}
  198. />
  199. )}
  200. </TransitionChart>
  201. </MiniChartPanel>
  202. );
  203. }
  204. export default DeviceClassBreakdownBarChart;