allEventsTable.tsx 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import {useEffect, useMemo, useState} from 'react';
  2. import type {Location} from 'history';
  3. import {getSampleEventQuery} from 'sentry/components/events/eventStatisticalDetector/eventComparison/eventDisplay';
  4. import LoadingError from 'sentry/components/loadingError';
  5. import {
  6. PlatformCategory,
  7. profiling as PROFILING_PLATFORMS,
  8. } from 'sentry/data/platformCategories';
  9. import {t} from 'sentry/locale';
  10. import type {EventTransaction, Group, Organization, PlatformKey} from 'sentry/types';
  11. import {IssueCategory, IssueType} from 'sentry/types';
  12. import EventView, {decodeSorts} from 'sentry/utils/discover/eventView';
  13. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  14. import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
  15. import {platformToCategory} from 'sentry/utils/platform';
  16. import {useApiQuery} from 'sentry/utils/queryClient';
  17. import {projectCanLinkToReplay} from 'sentry/utils/replays/projectSupportsReplay';
  18. import {useRoutes} from 'sentry/utils/useRoutes';
  19. import EventsTable from 'sentry/views/performance/transactionSummary/transactionEvents/eventsTable';
  20. interface Props {
  21. group: Group;
  22. issueId: string;
  23. location: Location;
  24. organization: Organization;
  25. excludedTags?: string[];
  26. }
  27. const makeGroupPreviewRequestUrl = ({groupId}: {groupId: string}) => {
  28. return `/issues/${groupId}/events/latest/`;
  29. };
  30. function AllEventsTable(props: Props) {
  31. const {location, organization, issueId, excludedTags, group} = props;
  32. const config = getConfigForIssueType(props.group, group.project);
  33. const [error, setError] = useState<string>('');
  34. const routes = useRoutes();
  35. const {fields, columnTitles} = getColumns(group, organization);
  36. const now = useMemo(() => Date.now(), []);
  37. const endpointUrl = makeGroupPreviewRequestUrl({
  38. groupId: group.id,
  39. });
  40. const queryEnabled = group.issueCategory === IssueCategory.PERFORMANCE;
  41. const {data, isLoading, isLoadingError} = useApiQuery<EventTransaction>([endpointUrl], {
  42. staleTime: 60000,
  43. enabled: queryEnabled,
  44. });
  45. // TODO: this is a temporary way to check whether
  46. // perf issue is backed by occurrences or transactions
  47. // Once migration to the issue platform is complete a call to /latest should be removed
  48. const groupIsOccurrenceBacked = !!data?.occurrence;
  49. const eventView: EventView = EventView.fromLocation(props.location);
  50. if (
  51. config.usesIssuePlatform ||
  52. (group.issueCategory === IssueCategory.PERFORMANCE && groupIsOccurrenceBacked)
  53. ) {
  54. eventView.dataset = DiscoverDatasets.ISSUE_PLATFORM;
  55. }
  56. eventView.fields = fields.map(fieldName => ({field: fieldName}));
  57. eventView.sorts = decodeSorts(location).filter(sort => fields.includes(sort.field));
  58. useEffect(() => {
  59. setError('');
  60. }, [eventView.query]);
  61. if (!eventView.sorts.length) {
  62. eventView.sorts = [{field: 'timestamp', kind: 'desc'}];
  63. }
  64. eventView.statsPeriod = '90d';
  65. const isRegressionIssue =
  66. group.issueType === IssueType.PERFORMANCE_DURATION_REGRESSION ||
  67. group.issueType === IssueType.PERFORMANCE_ENDPOINT_REGRESSION;
  68. let idQuery = `issue.id:${issueId}`;
  69. if (group.issueCategory === IssueCategory.PERFORMANCE && !groupIsOccurrenceBacked) {
  70. idQuery = `performance.issue_ids:${issueId} event.type:transaction`;
  71. } else if (isRegressionIssue && groupIsOccurrenceBacked) {
  72. const {transaction, aggregateRange2, breakpoint} =
  73. data?.occurrence?.evidenceData ?? {};
  74. // Surface the "bad" events that occur after the breakpoint
  75. idQuery = getSampleEventQuery({
  76. transaction,
  77. durationBaseline: aggregateRange2,
  78. addUpperBound: false,
  79. });
  80. eventView.dataset = DiscoverDatasets.DISCOVER;
  81. eventView.start = new Date(breakpoint * 1000).toISOString();
  82. eventView.end = new Date(now).toISOString();
  83. eventView.statsPeriod = undefined;
  84. }
  85. eventView.project = [parseInt(group.project.id, 10)];
  86. eventView.query = `${idQuery} ${props.location.query.query || ''}`;
  87. if (error || isLoadingError) {
  88. return (
  89. <LoadingError message={error || isLoadingError} onRetry={() => setError('')} />
  90. );
  91. }
  92. return (
  93. <EventsTable
  94. eventView={eventView}
  95. location={location}
  96. issueId={issueId}
  97. isRegressionIssue={isRegressionIssue}
  98. organization={organization}
  99. routes={routes}
  100. excludedTags={excludedTags}
  101. projectSlug={group.project.slug}
  102. customColumns={['minidump']}
  103. setError={(msg: string | undefined) => setError(msg ?? '')}
  104. transactionName=""
  105. columnTitles={columnTitles.slice()}
  106. referrer="api.issues.issue_events"
  107. isEventLoading={queryEnabled ? isLoading : false}
  108. />
  109. );
  110. }
  111. type ColumnInfo = {columnTitles: string[]; fields: string[]};
  112. const getColumns = (group: Group, organization: Organization): ColumnInfo => {
  113. const isPerfIssue = group.issueCategory === IssueCategory.PERFORMANCE;
  114. const isReplayEnabled =
  115. organization.features.includes('session-replay') &&
  116. projectCanLinkToReplay(group.project);
  117. // profiles only exist on transactions, so this only works with
  118. // performance issues, and not errors
  119. const isProfilingEnabled = isPerfIssue && organization.features.includes('profiling');
  120. const {fields: platformSpecificFields, columnTitles: platformSpecificColumnTitles} =
  121. getPlatformColumns(group.project.platform ?? group.platform, {
  122. isProfilingEnabled,
  123. isReplayEnabled,
  124. });
  125. const fields: string[] = [
  126. 'id',
  127. 'transaction',
  128. 'title',
  129. 'release',
  130. 'environment',
  131. 'user.display',
  132. 'device',
  133. 'os',
  134. ...platformSpecificFields,
  135. ...(isPerfIssue ? ['transaction.duration'] : []),
  136. 'timestamp',
  137. ];
  138. const columnTitles: string[] = [
  139. t('event id'),
  140. t('transaction'),
  141. t('title'),
  142. t('release'),
  143. t('environment'),
  144. t('user'),
  145. t('device'),
  146. t('os'),
  147. ...platformSpecificColumnTitles,
  148. ...(isPerfIssue ? [t('total duration')] : []),
  149. t('timestamp'),
  150. t('minidump'),
  151. ];
  152. return {
  153. fields,
  154. columnTitles,
  155. };
  156. };
  157. const getPlatformColumns = (
  158. platform: PlatformKey | undefined,
  159. options: {isProfilingEnabled: boolean; isReplayEnabled: boolean}
  160. ): ColumnInfo => {
  161. const backendServerlessColumnInfo = {
  162. fields: ['url', 'runtime'],
  163. columnTitles: [t('url'), t('runtime')],
  164. };
  165. const categoryToColumnMap: Record<PlatformCategory, ColumnInfo> = {
  166. [PlatformCategory.BACKEND]: backendServerlessColumnInfo,
  167. [PlatformCategory.SERVERLESS]: backendServerlessColumnInfo,
  168. [PlatformCategory.FRONTEND]: {
  169. fields: ['url', 'browser'],
  170. columnTitles: [t('url'), t('browser')],
  171. },
  172. [PlatformCategory.MOBILE]: {
  173. fields: ['url'],
  174. columnTitles: [t('url')],
  175. },
  176. [PlatformCategory.DESKTOP]: {
  177. fields: [],
  178. columnTitles: [],
  179. },
  180. [PlatformCategory.OTHER]: {
  181. fields: [],
  182. columnTitles: [],
  183. },
  184. };
  185. const platformCategory = platformToCategory(platform);
  186. const platformColumns = categoryToColumnMap[platformCategory];
  187. if (options.isReplayEnabled) {
  188. platformColumns.fields.push('replayId');
  189. platformColumns.columnTitles.push(t('replay'));
  190. }
  191. if (options.isProfilingEnabled && platform && PROFILING_PLATFORMS.includes(platform)) {
  192. platformColumns.columnTitles.push(t('profile'));
  193. platformColumns.fields.push('profile.id');
  194. }
  195. return platformColumns;
  196. };
  197. export default AllEventsTable;