screensOverview.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. import {useEffect, useMemo, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import type {Location} from 'history';
  4. import SearchBar from 'sentry/components/performance/searchBar';
  5. import {t} from 'sentry/locale';
  6. import {space} from 'sentry/styles/space';
  7. import type {PageFilters} from 'sentry/types/core';
  8. import type {NewQuery} from 'sentry/types/organization';
  9. import {defined} from 'sentry/utils';
  10. import type {TableData} from 'sentry/utils/discover/discoverQuery';
  11. import EventView, {type MetaType} from 'sentry/utils/discover/eventView';
  12. import {getAggregateAlias} from 'sentry/utils/discover/fields';
  13. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  14. import {decodeScalar, decodeSorts} from 'sentry/utils/queryString';
  15. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  16. import {useLocation} from 'sentry/utils/useLocation';
  17. import useOrganization from 'sentry/utils/useOrganization';
  18. import usePageFilters from 'sentry/utils/usePageFilters';
  19. import useRouter from 'sentry/utils/useRouter';
  20. import useCrossPlatformProject from 'sentry/views/insights/mobile/common/queries/useCrossPlatformProject';
  21. import {getFreeTextFromQuery} from 'sentry/views/insights/mobile/screenload/components/screensView';
  22. import {useTableQuery} from 'sentry/views/insights/mobile/screenload/components/tables/screensTable';
  23. import ScreensOverviewTable from 'sentry/views/insights/mobile/screens/components/screensOverviewTable';
  24. import {Referrer} from 'sentry/views/insights/mobile/screens/referrers';
  25. import {SpanMetricsField} from 'sentry/views/insights/types';
  26. import {prepareQueryForLandingPage} from 'sentry/views/performance/data';
  27. import {getTransactionSearchQuery} from 'sentry/views/performance/utils';
  28. function useMetrics(
  29. dataset: DiscoverDatasets,
  30. fields: string[],
  31. selectedPlatform: string | undefined,
  32. selection: PageFilters,
  33. location: Location,
  34. sortedBy: string | undefined,
  35. enabled: boolean,
  36. screens: string[]
  37. ) {
  38. const {query: locationQuery} = location;
  39. const query = new MutableSearch(['transaction.op:ui.load']);
  40. const searchQuery = decodeScalar(locationQuery.query, '');
  41. if (searchQuery) {
  42. query.addStringFilter(prepareQueryForLandingPage(searchQuery, false));
  43. }
  44. if (selectedPlatform) {
  45. query.addFilterValue('os.name', selectedPlatform);
  46. }
  47. let queryString = query.formatString();
  48. if (screens.length > 0) {
  49. const screenFilter = `transaction:[${screens.map(name => `"${name}"`).join()}]`;
  50. queryString = queryString + ' ' + screenFilter;
  51. }
  52. const newQuery: NewQuery = {
  53. name: '',
  54. fields,
  55. query: queryString,
  56. dataset,
  57. version: 2,
  58. projects: selection.projects,
  59. };
  60. if (sortedBy) {
  61. newQuery.orderby = sortedBy;
  62. }
  63. const tableEventView = EventView.fromNewQueryWithLocation(newQuery, location);
  64. return {
  65. eventView: tableEventView,
  66. ...useTableQuery({
  67. eventView: tableEventView,
  68. enabled,
  69. referrer: Referrer.SCREENS_SCREEN_TABLE,
  70. }),
  71. };
  72. }
  73. export function ScreensOverview() {
  74. const router = useRouter();
  75. const {selection} = usePageFilters();
  76. const location = useLocation();
  77. const organization = useOrganization();
  78. const {isProjectCrossPlatform, selectedPlatform} = useCrossPlatformProject();
  79. const [visibleScreens, setVisibleScreens] = useState<string[]>([]);
  80. const sortedBy = decodeScalar(location.query.sort, '-count');
  81. const sortField = decodeSorts([sortedBy])[0]!.field;
  82. const transactionMetricsDataset = DiscoverDatasets.METRICS;
  83. const transactionMetricsFields = [
  84. SpanMetricsField.PROJECT_ID,
  85. SpanMetricsField.TRANSACTION,
  86. `count()`,
  87. 'avg(measurements.app_start_cold)',
  88. 'avg(measurements.app_start_warm)',
  89. `avg(measurements.time_to_initial_display)`,
  90. `avg(measurements.time_to_full_display)`,
  91. ];
  92. const spanMetricsDataset = DiscoverDatasets.SPANS_METRICS;
  93. const spanMetricsFields = [
  94. SpanMetricsField.PROJECT_ID,
  95. SpanMetricsField.TRANSACTION,
  96. `division(mobile.slow_frames,mobile.total_frames)`,
  97. `division(mobile.frozen_frames,mobile.total_frames)`,
  98. `avg(mobile.frames_delay)`,
  99. ];
  100. const isSpanPrimary = spanMetricsFields.some(
  101. field => getAggregateAlias(field) === sortField
  102. );
  103. const primaryDataset = isSpanPrimary ? spanMetricsDataset : transactionMetricsDataset;
  104. const primaryFields = isSpanPrimary ? spanMetricsFields : transactionMetricsFields;
  105. const secondaryDataset = isSpanPrimary ? transactionMetricsDataset : spanMetricsDataset;
  106. const secondaryFields = isSpanPrimary ? transactionMetricsFields : spanMetricsFields;
  107. const hasVisibleScreens = visibleScreens.length > 0;
  108. const {
  109. data: primaryData,
  110. isPending: primaryLoading,
  111. pageLinks: primaryLinks,
  112. eventView: primaryEventView,
  113. } = useMetrics(
  114. primaryDataset,
  115. primaryFields,
  116. isProjectCrossPlatform ? selectedPlatform : undefined,
  117. selection,
  118. location,
  119. sortedBy,
  120. true,
  121. []
  122. );
  123. const {data: secondaryData, isPending: secondaryLoading} = useMetrics(
  124. secondaryDataset,
  125. secondaryFields,
  126. isProjectCrossPlatform ? selectedPlatform : undefined,
  127. selection,
  128. location,
  129. undefined,
  130. hasVisibleScreens,
  131. visibleScreens
  132. );
  133. useEffect(() => {
  134. if (primaryData) {
  135. const screens: string[] = [];
  136. primaryData?.data.forEach(row => {
  137. if (row.transaction) {
  138. screens.push(String(row.transaction));
  139. }
  140. });
  141. setVisibleScreens(screens);
  142. }
  143. }, [primaryData]);
  144. const tableSearchFilters = new MutableSearch(['transaction.op:ui.load']);
  145. const derivedQuery = getTransactionSearchQuery(location, primaryEventView.query);
  146. const combinedData = useMemo((): TableData | undefined => {
  147. if (defined(primaryData) && defined(secondaryData)) {
  148. const meta: MetaType = {};
  149. meta.units = {
  150. ...secondaryData.meta?.units,
  151. ...primaryData.meta?.units,
  152. };
  153. meta.fields = {
  154. ...secondaryData.meta?.fields,
  155. ...primaryData.meta?.fields,
  156. };
  157. const data = primaryData.data.map(row => {
  158. const matchingRow = secondaryData.data.find(
  159. metricRow => metricRow.transaction === row.transaction
  160. );
  161. if (matchingRow) {
  162. return {
  163. ...matchingRow,
  164. ...row,
  165. };
  166. }
  167. return row;
  168. });
  169. return {
  170. data,
  171. meta,
  172. };
  173. }
  174. return primaryData;
  175. }, [primaryData, secondaryData]);
  176. const loading = primaryLoading || (hasVisibleScreens && secondaryLoading);
  177. return (
  178. <Container>
  179. <SearchBar
  180. eventView={primaryEventView}
  181. onSearch={search => {
  182. router.push({
  183. pathname: router.location.pathname,
  184. query: {
  185. ...location.query,
  186. cursor: undefined,
  187. query: String(search).trim() || undefined,
  188. },
  189. });
  190. }}
  191. organization={organization}
  192. query={getFreeTextFromQuery(derivedQuery)!}
  193. placeholder={t('Search for Screen')}
  194. additionalConditions={tableSearchFilters}
  195. />
  196. <Container>
  197. <ScreensOverviewTable
  198. eventView={primaryEventView}
  199. data={combinedData}
  200. isLoading={loading}
  201. pageLinks={primaryLinks}
  202. />
  203. </Container>
  204. </Container>
  205. );
  206. }
  207. const Container = styled('div')`
  208. padding-top: ${space(1)};
  209. `;
  210. export default ScreensOverview;