mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-10-30 14:49:14 +00:00
269 lines
8.3 KiB
TypeScript
269 lines
8.3 KiB
TypeScript
import {Fragment, useEffect} from 'react';
|
|
import type {RouteComponentProps} from 'react-router';
|
|
import {browserHistory} from 'react-router';
|
|
import styled from '@emotion/styled';
|
|
import isEqual from 'lodash/isEqual';
|
|
|
|
import ArchivedBox from 'sentry/components/archivedBox';
|
|
import GroupEventDetailsLoadingError from 'sentry/components/errors/groupEventDetailsLoadingError';
|
|
import {withMeta} from 'sentry/components/events/meta/metaProxy';
|
|
import HookOrDefault from 'sentry/components/hookOrDefault';
|
|
import * as Layout from 'sentry/components/layouts/thirds';
|
|
import LoadingIndicator from 'sentry/components/loadingIndicator';
|
|
import {TransactionProfileIdProvider} from 'sentry/components/profiling/transactionProfileIdProvider';
|
|
import ResolutionBox from 'sentry/components/resolutionBox';
|
|
import useSentryAppComponentsData from 'sentry/stores/useSentryAppComponentsData';
|
|
import {space} from 'sentry/styles/space';
|
|
import type {
|
|
Group,
|
|
GroupActivityReprocess,
|
|
GroupReprocessing,
|
|
Organization,
|
|
Project,
|
|
} from 'sentry/types';
|
|
import type {Event} from 'sentry/types/event';
|
|
import {defined} from 'sentry/utils';
|
|
import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
|
|
import {QuickTraceContext} from 'sentry/utils/performance/quickTrace/quickTraceContext';
|
|
import QuickTraceQuery from 'sentry/utils/performance/quickTrace/quickTraceQuery';
|
|
import {VisuallyCompleteWithData} from 'sentry/utils/performanceForSentry';
|
|
import usePrevious from 'sentry/utils/usePrevious';
|
|
import {normalizeUrl} from 'sentry/utils/withDomainRequired';
|
|
import GroupEventDetailsContent from 'sentry/views/issueDetails/groupEventDetails/groupEventDetailsContent';
|
|
import GroupEventHeader from 'sentry/views/issueDetails/groupEventHeader';
|
|
import GroupSidebar from 'sentry/views/issueDetails/groupSidebar';
|
|
|
|
import ReprocessingProgress from '../reprocessingProgress';
|
|
import {
|
|
getEventEnvironment,
|
|
getGroupMostRecentActivity,
|
|
ReprocessingStatus,
|
|
useEnvironmentsFromUrl,
|
|
} from '../utils';
|
|
|
|
const EscalatingIssuesFeedback = HookOrDefault({
|
|
hookName: 'component:escalating-issues-banner-feedback',
|
|
});
|
|
|
|
export interface GroupEventDetailsProps
|
|
extends RouteComponentProps<{groupId: string; eventId?: string}, {}> {
|
|
eventError: boolean;
|
|
group: Group;
|
|
groupReprocessingStatus: ReprocessingStatus;
|
|
loadingEvent: boolean;
|
|
onRetry: () => void;
|
|
organization: Organization;
|
|
project: Project;
|
|
event?: Event;
|
|
}
|
|
|
|
function GroupEventDetails(props: GroupEventDetailsProps) {
|
|
const {
|
|
group,
|
|
project,
|
|
organization,
|
|
location,
|
|
event,
|
|
groupReprocessingStatus,
|
|
loadingEvent,
|
|
onRetry,
|
|
eventError,
|
|
params,
|
|
} = props;
|
|
const eventWithMeta = withMeta(event);
|
|
|
|
// Reprocessing
|
|
const hasReprocessingV2Feature = organization.features?.includes('reprocessing-v2');
|
|
const {activity: activities} = group;
|
|
const mostRecentActivity = getGroupMostRecentActivity(activities);
|
|
const projectId = project.id;
|
|
const environments = useEnvironmentsFromUrl();
|
|
const prevEnvironment = usePrevious(environments);
|
|
const prevEvent = usePrevious(event);
|
|
|
|
// load the data
|
|
useSentryAppComponentsData({projectId});
|
|
|
|
// If environments are being actively changed and will no longer contain the
|
|
// current event's environment, redirect to latest
|
|
useEffect(() => {
|
|
const environmentsHaveChanged = !isEqual(prevEnvironment, environments);
|
|
// If environments are being actively changed and will no longer contain the
|
|
// current event's environment, redirect to latest
|
|
if (
|
|
environmentsHaveChanged &&
|
|
prevEvent &&
|
|
params.eventId &&
|
|
!['latest', 'oldest'].includes(params.eventId)
|
|
) {
|
|
const shouldRedirect =
|
|
environments.length > 0 &&
|
|
!environments.find(env => env === getEventEnvironment(prevEvent as Event));
|
|
|
|
if (shouldRedirect) {
|
|
browserHistory.replace(
|
|
normalizeUrl({
|
|
pathname: `/organizations/${organization.slug}/issues/${params.groupId}/`,
|
|
query: location.query,
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
}, [
|
|
prevEnvironment,
|
|
environments,
|
|
location.query,
|
|
organization.slug,
|
|
params,
|
|
prevEvent,
|
|
]);
|
|
|
|
const renderGroupStatusBanner = () => {
|
|
if (group.status === 'ignored') {
|
|
return (
|
|
<GroupStatusBannerWrapper>
|
|
<ArchivedBox
|
|
substatus={group.substatus}
|
|
statusDetails={group.statusDetails}
|
|
organization={organization}
|
|
/>
|
|
</GroupStatusBannerWrapper>
|
|
);
|
|
}
|
|
|
|
if (group.status === 'resolved') {
|
|
return (
|
|
<GroupStatusBannerWrapper>
|
|
<ResolutionBox
|
|
statusDetails={group.statusDetails}
|
|
activities={group.activity}
|
|
projectId={project.id}
|
|
/>
|
|
</GroupStatusBannerWrapper>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const renderContent = () => {
|
|
if (loadingEvent) {
|
|
return <LoadingIndicator />;
|
|
}
|
|
|
|
if (eventError) {
|
|
return (
|
|
<GroupEventDetailsLoadingError environments={environments} onRetry={onRetry} />
|
|
);
|
|
}
|
|
|
|
return (
|
|
<GroupEventDetailsContent group={group} event={eventWithMeta} project={project} />
|
|
);
|
|
};
|
|
|
|
const issueTypeConfig = getConfigForIssueType(group, project);
|
|
|
|
return (
|
|
<TransactionProfileIdProvider
|
|
projectId={event?.projectID}
|
|
transactionId={event?.type === 'transaction' ? event.id : undefined}
|
|
timestamp={event?.dateReceived}
|
|
>
|
|
<VisuallyCompleteWithData
|
|
id="IssueDetails-EventBody"
|
|
hasData={!loadingEvent && !eventError && defined(eventWithMeta)}
|
|
isLoading={loadingEvent}
|
|
>
|
|
<StyledLayoutBody data-test-id="group-event-details">
|
|
{hasReprocessingV2Feature &&
|
|
groupReprocessingStatus === ReprocessingStatus.REPROCESSING ? (
|
|
<ReprocessingProgress
|
|
totalEvents={(mostRecentActivity as GroupActivityReprocess).data.eventCount}
|
|
pendingEvents={
|
|
(group.statusDetails as GroupReprocessing['statusDetails']).pendingEvents
|
|
}
|
|
/>
|
|
) : (
|
|
<Fragment>
|
|
<QuickTraceQuery
|
|
event={eventWithMeta}
|
|
location={location}
|
|
orgSlug={organization.slug}
|
|
>
|
|
{results => {
|
|
return (
|
|
<StyledLayoutMain>
|
|
{renderGroupStatusBanner()}
|
|
<EscalatingIssuesFeedback
|
|
organization={organization}
|
|
group={group}
|
|
/>
|
|
<QuickTraceContext.Provider value={results}>
|
|
{eventWithMeta && issueTypeConfig.stats.enabled && (
|
|
<GroupEventHeader
|
|
group={group}
|
|
event={eventWithMeta}
|
|
project={project}
|
|
/>
|
|
)}
|
|
{renderContent()}
|
|
</QuickTraceContext.Provider>
|
|
</StyledLayoutMain>
|
|
);
|
|
}}
|
|
</QuickTraceQuery>
|
|
<StyledLayoutSide>
|
|
<GroupSidebar
|
|
organization={organization}
|
|
project={project}
|
|
group={group}
|
|
event={eventWithMeta}
|
|
environments={environments}
|
|
/>
|
|
</StyledLayoutSide>
|
|
</Fragment>
|
|
)}
|
|
</StyledLayoutBody>
|
|
</VisuallyCompleteWithData>
|
|
</TransactionProfileIdProvider>
|
|
);
|
|
}
|
|
|
|
const StyledLayoutBody = styled(Layout.Body)`
|
|
/* Makes the borders align correctly */
|
|
padding: 0 !important;
|
|
@media (min-width: ${p => p.theme.breakpoints.large}) {
|
|
align-content: stretch;
|
|
}
|
|
`;
|
|
|
|
const GroupStatusBannerWrapper = styled('div')`
|
|
margin-bottom: ${space(2)};
|
|
`;
|
|
|
|
const StyledLayoutMain = styled(Layout.Main)`
|
|
padding-top: ${space(2)};
|
|
|
|
@media (max-width: ${p => p.theme.breakpoints.medium}) {
|
|
padding-top: ${space(1)};
|
|
}
|
|
|
|
@media (min-width: ${p => p.theme.breakpoints.large}) {
|
|
border-right: 1px solid ${p => p.theme.border};
|
|
padding-right: 0;
|
|
}
|
|
`;
|
|
|
|
const StyledLayoutSide = styled(Layout.Side)`
|
|
padding: ${space(3)} ${space(2)} ${space(3)};
|
|
|
|
@media (min-width: ${p => p.theme.breakpoints.large}) {
|
|
padding-right: ${space(4)};
|
|
padding-left: 0;
|
|
}
|
|
`;
|
|
|
|
export default GroupEventDetails;
|