allEventsTable.tsx 6.1 KB

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