screensOverview.tsx 7.2 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: fields,
  55. query: queryString,
  56. dataset: 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: 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. `avg(mobile.slow_frames)`,
  97. `avg(mobile.frozen_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 {
  108. data: primaryData,
  109. isPending: primaryLoading,
  110. pageLinks: primaryLinks,
  111. eventView: primaryEventView,
  112. } = useMetrics(
  113. primaryDataset,
  114. primaryFields,
  115. isProjectCrossPlatform ? selectedPlatform : undefined,
  116. selection,
  117. location,
  118. sortedBy,
  119. true,
  120. []
  121. );
  122. const {data: secondaryData, isPending: secondaryLoading} = useMetrics(
  123. secondaryDataset,
  124. secondaryFields,
  125. isProjectCrossPlatform ? selectedPlatform : undefined,
  126. selection,
  127. location,
  128. undefined,
  129. visibleScreens.length > 0,
  130. visibleScreens
  131. );
  132. useEffect(() => {
  133. if (primaryData) {
  134. const screens: string[] = new Array();
  135. primaryData?.data.forEach(row => {
  136. if (row.transaction) {
  137. screens.push(String(row.transaction));
  138. }
  139. });
  140. setVisibleScreens(screens);
  141. }
  142. }, [primaryData]);
  143. const tableSearchFilters = new MutableSearch(['transaction.op:ui.load']);
  144. const derivedQuery = getTransactionSearchQuery(location, primaryEventView.query);
  145. const combinedData = useMemo((): TableData | undefined => {
  146. if (defined(primaryData) && defined(secondaryData)) {
  147. const meta: MetaType = {};
  148. meta.units = {
  149. ...secondaryData.meta?.units,
  150. ...primaryData.meta?.units,
  151. };
  152. meta.fields = {
  153. ...secondaryData.meta?.fields,
  154. ...primaryData.meta?.fields,
  155. };
  156. const data = primaryData.data.map(row => {
  157. const matchingRow = secondaryData.data.find(
  158. metricRow => metricRow.transaction === row.transaction
  159. );
  160. if (matchingRow) {
  161. return {
  162. ...matchingRow,
  163. ...row,
  164. };
  165. }
  166. return row;
  167. });
  168. return {
  169. data,
  170. meta,
  171. };
  172. }
  173. return primaryData;
  174. }, [primaryData, secondaryData]);
  175. const loading = primaryLoading || secondaryLoading;
  176. return (
  177. <Container>
  178. <SearchBar
  179. eventView={primaryEventView}
  180. onSearch={search => {
  181. router.push({
  182. pathname: router.location.pathname,
  183. query: {
  184. ...location.query,
  185. cursor: undefined,
  186. query: String(search).trim() || undefined,
  187. },
  188. });
  189. }}
  190. organization={organization}
  191. query={getFreeTextFromQuery(derivedQuery)}
  192. placeholder={t('Search for Screen')}
  193. additionalConditions={tableSearchFilters}
  194. />
  195. <Container>
  196. <ScreensOverviewTable
  197. eventView={primaryEventView}
  198. data={combinedData}
  199. isLoading={loading}
  200. pageLinks={primaryLinks}
  201. />
  202. </Container>
  203. </Container>
  204. );
  205. }
  206. const Container = styled('div')`
  207. padding-top: ${space(1)};
  208. `;
  209. export default ScreensOverview;