import {Fragment, useState} from 'react'; import styled from '@emotion/styled'; import {FeatureFeedback} from 'sentry/components/featureFeedback'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Event, EventGroupVariant} from 'sentry/types/event'; import {EventGroupVariantType} from 'sentry/types/event'; import type {Group} from 'sentry/types/group'; import {IssueCategory} from 'sentry/types/group'; import {useApiQuery} from 'sentry/utils/queryClient'; import useOrganization from 'sentry/utils/useOrganization'; import SectionToggleButton from 'sentry/views/issueDetails/sectionToggleButton'; import {FoldSectionKey} from 'sentry/views/issueDetails/streamline/foldSection'; import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection'; import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils'; import GroupingConfigSelect from './groupingConfigSelect'; import GroupVariant from './groupingVariant'; const groupingFeedbackTypes = [ t('Too eager grouping'), t('Too specific grouping'), t('Other grouping issue'), ]; type GroupingInfoProps = { event: Event; projectSlug: string; showGroupingConfig: boolean; group?: Group; }; type EventGroupingInfoResponse = { [variant: string]: EventGroupVariant; }; function generatePerformanceGroupInfo({ event, group, }: { event: Event; group: Group; }): EventGroupingInfoResponse | null { if (!event.occurrence) { return null; } const {evidenceData} = event.occurrence; const hash = event.occurrence?.fingerprint[0] || ''; return group ? { [group.issueType]: { description: t('performance problem'), hash: event.occurrence?.fingerprint[0] || '', hashMismatch: false, key: group.issueType, type: EventGroupVariantType.PERFORMANCE_PROBLEM, evidence: { op: evidenceData?.op, parent_span_ids: evidenceData?.parentSpanIds, cause_span_ids: evidenceData?.causeSpanIds, offender_span_ids: evidenceData?.offenderSpanIds, desc: t('performance problem'), fingerprint: hash, }, }, } : null; } function GroupConfigSelect({ event, configOverride, setConfigOverride, }: { configOverride: string | null; event: Event; setConfigOverride: (value: string) => void; }) { if (!event.groupingConfig) { return null; } const configId = configOverride ?? event.groupingConfig?.id; return ( setConfigOverride(selection.value)} /> ); } function GroupInfoSummary({groupInfo}: {groupInfo: EventGroupingInfoResponse | null}) { const groupedBy = groupInfo ? Object.values(groupInfo) .filter(variant => variant.hash !== null && variant.description !== null) .map(variant => variant.description) .sort((a, b) => a!.toLowerCase().localeCompare(b!.toLowerCase())) .join(', ') : t('nothing'); return (

{t('Grouped by:')} {groupedBy}

); } export function EventGroupingInfo({ event, projectSlug, showGroupingConfig, group, }: GroupingInfoProps) { const organization = useOrganization(); const [isOpen, setIsOpen] = useState(false); const hasStreamlinedUI = useHasStreamlinedUI(); const [configOverride, setConfigOverride] = useState(null); const hasPerformanceGrouping = event.occurrence && group?.issueCategory === IssueCategory.PERFORMANCE && event.type === 'transaction'; const {data, isLoading, isError, isSuccess} = useApiQuery( [ `/projects/${organization.slug}/${projectSlug}/events/${event.id}/grouping-info/`, {query: configOverride ? {config: configOverride} : {}}, ], {enabled: !hasPerformanceGrouping, staleTime: Infinity} ); const groupInfo = hasPerformanceGrouping ? generatePerformanceGroupInfo({group, event}) : data ?? null; const variants = groupInfo ? Object.values(groupInfo).sort((a, b) => a.hash && !b.hash ? -1 : a.description ?.toLowerCase() .localeCompare(b.description?.toLowerCase() ?? '') ?? 1 ) : []; const openState = hasStreamlinedUI ? true : isOpen; return ( ) } type={FoldSectionKey.GROUPING_INFO} > {!openState ? : null} {openState ? ( {hasStreamlinedUI ? : null}
{showGroupingConfig && ( )}
{isError ? ( ) : null} {isLoading && !hasPerformanceGrouping ? : null} {hasPerformanceGrouping || isSuccess ? variants.map((variant, index) => ( {index < variants.length - 1 && } )) : null}
) : null}
); } const ConfigHeader = styled('div')` display: flex; align-items: center; justify-content: space-between; gap: ${space(1)}; margin-bottom: ${space(2)}; `; export const GroupingConfigItem = styled('span')<{ isActive?: boolean; isHidden?: boolean; }>` font-family: ${p => p.theme.text.familyMono}; opacity: ${p => (p.isHidden ? 0.5 : null)}; font-weight: ${p => (p.isActive ? 'bold' : null)}; font-size: ${p => p.theme.fontSizeSmall}; `; const VariantDivider = styled('hr')` padding-top: ${space(1)}; border-top: 1px solid ${p => p.theme.border}; `;