123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- import {useEffect, useMemo, useState} from 'react';
- import {Location} from 'history';
- import {getSampleEventQuery} from 'sentry/components/events/eventStatisticalDetector/eventComparison/eventDisplay';
- import LoadingError from 'sentry/components/loadingError';
- import {
- PlatformCategory,
- profiling as PROFILING_PLATFORMS,
- } from 'sentry/data/platformCategories';
- import {t} from 'sentry/locale';
- import {
- EventTransaction,
- Group,
- IssueCategory,
- IssueType,
- Organization,
- PlatformKey,
- } from 'sentry/types';
- import EventView, {decodeSorts} from 'sentry/utils/discover/eventView';
- import {DiscoverDatasets} from 'sentry/utils/discover/types';
- import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
- import {platformToCategory} from 'sentry/utils/platform';
- import {useApiQuery} from 'sentry/utils/queryClient';
- import {projectCanLinkToReplay} from 'sentry/utils/replays/projectSupportsReplay';
- import {useRoutes} from 'sentry/utils/useRoutes';
- import EventsTable from 'sentry/views/performance/transactionSummary/transactionEvents/eventsTable';
- interface Props {
- group: Group;
- issueId: string;
- location: Location;
- organization: Organization;
- excludedTags?: string[];
- }
- const makeGroupPreviewRequestUrl = ({groupId}: {groupId: string}) => {
- return `/issues/${groupId}/events/latest/`;
- };
- function AllEventsTable(props: Props) {
- const {location, organization, issueId, excludedTags, group} = props;
- const config = getConfigForIssueType(props.group);
- const [error, setError] = useState<string>('');
- const routes = useRoutes();
- const {fields, columnTitles} = getColumns(group, organization);
- const now = useMemo(() => Date.now(), []);
- const endpointUrl = makeGroupPreviewRequestUrl({
- groupId: group.id,
- });
- const queryEnabled = group.issueCategory === IssueCategory.PERFORMANCE;
- const {data, isLoading, isLoadingError} = useApiQuery<EventTransaction>([endpointUrl], {
- staleTime: 60000,
- enabled: queryEnabled,
- });
- // TODO: this is a temporary way to check whether
- // perf issue is backed by occurrences or transactions
- // Once migration to the issue platform is complete a call to /latest should be removed
- const groupIsOccurrenceBacked = !!data?.occurrence;
- const eventView: EventView = EventView.fromLocation(props.location);
- if (
- config.usesIssuePlatform ||
- (group.issueCategory === IssueCategory.PERFORMANCE && groupIsOccurrenceBacked)
- ) {
- eventView.dataset = DiscoverDatasets.ISSUE_PLATFORM;
- }
- eventView.fields = fields.map(fieldName => ({field: fieldName}));
- eventView.sorts = decodeSorts(location).filter(sort => fields.includes(sort.field));
- useEffect(() => {
- setError('');
- }, [eventView.query]);
- if (!eventView.sorts.length) {
- eventView.sorts = [{field: 'timestamp', kind: 'desc'}];
- }
- eventView.statsPeriod = '90d';
- let idQuery = `issue.id:${issueId}`;
- if (group.issueCategory === IssueCategory.PERFORMANCE && !groupIsOccurrenceBacked) {
- idQuery = `performance.issue_ids:${issueId} event.type:transaction`;
- } else if (
- group.issueType === IssueType.PERFORMANCE_DURATION_REGRESSION &&
- groupIsOccurrenceBacked
- ) {
- const {transaction, aggregateRange2, breakpoint} =
- data?.occurrence?.evidenceData ?? {};
- // Surface the "bad" events that occur after the breakpoint
- idQuery = getSampleEventQuery({
- transaction,
- durationBaseline: aggregateRange2,
- addUpperBound: false,
- });
- eventView.dataset = DiscoverDatasets.DISCOVER;
- eventView.start = new Date(breakpoint * 1000).toISOString();
- eventView.end = new Date(now).toISOString();
- eventView.statsPeriod = undefined;
- }
- eventView.project = [parseInt(group.project.id, 10)];
- eventView.query = `${idQuery} ${props.location.query.query || ''}`;
- if (error || isLoadingError) {
- return (
- <LoadingError message={error || isLoadingError} onRetry={() => setError('')} />
- );
- }
- return (
- <EventsTable
- eventView={eventView}
- location={location}
- issueId={issueId}
- organization={organization}
- routes={routes}
- excludedTags={excludedTags}
- projectSlug={group.project.slug}
- customColumns={['minidump']}
- setError={(msg: string | undefined) => setError(msg ?? '')}
- transactionName=""
- columnTitles={columnTitles.slice()}
- referrer="api.issues.issue_events"
- isEventLoading={queryEnabled ? isLoading : false}
- />
- );
- }
- type ColumnInfo = {columnTitles: string[]; fields: string[]};
- const getColumns = (group: Group, organization: Organization): ColumnInfo => {
- const isPerfIssue = group.issueCategory === IssueCategory.PERFORMANCE;
- const isReplayEnabled =
- organization.features.includes('session-replay') &&
- projectCanLinkToReplay(group.project);
- // profiles only exist on transactions, so this only works with
- // performance issues, and not errors
- const isProfilingEnabled = isPerfIssue && organization.features.includes('profiling');
- const {fields: platformSpecificFields, columnTitles: platformSpecificColumnTitles} =
- getPlatformColumns(group.project.platform ?? group.platform, {
- isProfilingEnabled,
- isReplayEnabled,
- });
- const fields: string[] = [
- 'id',
- 'transaction',
- 'title',
- 'release',
- 'environment',
- 'user.display',
- 'device',
- 'os',
- ...platformSpecificFields,
- ...(isPerfIssue ? ['transaction.duration'] : []),
- 'timestamp',
- ];
- const columnTitles: string[] = [
- t('event id'),
- t('transaction'),
- t('title'),
- t('release'),
- t('environment'),
- t('user'),
- t('device'),
- t('os'),
- ...platformSpecificColumnTitles,
- ...(isPerfIssue ? [t('total duration')] : []),
- t('timestamp'),
- t('minidump'),
- ];
- return {
- fields,
- columnTitles,
- };
- };
- const getPlatformColumns = (
- platform: PlatformKey | undefined,
- options: {isProfilingEnabled: boolean; isReplayEnabled: boolean}
- ): ColumnInfo => {
- const backendServerlessColumnInfo = {
- fields: ['url', 'runtime'],
- columnTitles: [t('url'), t('runtime')],
- };
- const categoryToColumnMap: Record<PlatformCategory, ColumnInfo> = {
- [PlatformCategory.BACKEND]: backendServerlessColumnInfo,
- [PlatformCategory.SERVERLESS]: backendServerlessColumnInfo,
- [PlatformCategory.FRONTEND]: {
- fields: ['url', 'browser'],
- columnTitles: [t('url'), t('browser')],
- },
- [PlatformCategory.MOBILE]: {
- fields: ['url'],
- columnTitles: [t('url')],
- },
- [PlatformCategory.DESKTOP]: {
- fields: [],
- columnTitles: [],
- },
- [PlatformCategory.OTHER]: {
- fields: [],
- columnTitles: [],
- },
- };
- const platformCategory = platformToCategory(platform);
- const platformColumns = categoryToColumnMap[platformCategory];
- if (options.isReplayEnabled) {
- platformColumns.fields.push('replayId');
- platformColumns.columnTitles.push(t('replay'));
- }
- if (options.isProfilingEnabled && platform && PROFILING_PLATFORMS.includes(platform)) {
- platformColumns.columnTitles.push(t('profile'));
- platformColumns.fields.push('profile.id');
- }
- return platformColumns;
- };
- export default AllEventsTable;
|