allEventsTable.tsx 6.2 KB

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