allEventsTable.tsx 7.5 KB

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