diff --git a/static/app/views/issueDetails/activitySection/groupActivityIcons.tsx b/static/app/views/issueDetails/activitySection/groupActivityIcons.tsx index 779ce980fade..15f59aa4a71c 100644 --- a/static/app/views/issueDetails/activitySection/groupActivityIcons.tsx +++ b/static/app/views/issueDetails/activitySection/groupActivityIcons.tsx @@ -1,3 +1,4 @@ +import {createElement} from 'react'; import styled from '@emotion/styled'; import {SentryAppAvatar, UserAvatar} from '@sentry/scraps/avatar'; @@ -211,3 +212,34 @@ const StyledUserAvatar = styled(UserAvatar)` margin: ${p => p.theme.space['2xs']}; } `; + +/** + * Renders the activity icon for a given icon mapping and activity item without + * creating a component type during render. This avoids the React + * "static-component-definitions" violation that occurs when a variable holding + * a dynamically-selected component type is used as a JSX element type. + */ +export function renderActivityIcon( + iconMapping: IconWithDefaultProps | undefined, + item: Pick, + iconProps: Record +): React.ReactNode { + if (!iconMapping) { + return null; + } + + const {componentFunction, Component, defaultProps, propsFunction} = iconMapping; + const resolvedComponent = componentFunction + ? componentFunction({data: item.data, user: item.user, sentry_app: item.sentry_app}) + : Component; + + if (!resolvedComponent) { + return null; + } + + return createElement(resolvedComponent, { + ...defaultProps, + ...propsFunction?.(item.data), + ...iconProps, + }); +} diff --git a/static/app/views/issueList/progressActivityTooltip.tsx b/static/app/views/issueList/progressActivityTooltip.tsx index f81af33c2ac1..7cb9c4a5fbda 100644 --- a/static/app/views/issueList/progressActivityTooltip.tsx +++ b/static/app/views/issueList/progressActivityTooltip.tsx @@ -18,7 +18,10 @@ import {useOrganization} from 'sentry/utils/useOrganization'; import {useTeamsById} from 'sentry/utils/useTeamsById'; import {getActivityColorConfig} from 'sentry/views/issueDetails/activitySection/activityColorConfig'; import {ActivityMarker} from 'sentry/views/issueDetails/activitySection/activityMarker'; -import {groupActivityTypeIconMapping} from 'sentry/views/issueDetails/activitySection/groupActivityIcons'; +import { + groupActivityTypeIconMapping, + renderActivityIcon, +} from 'sentry/views/issueDetails/activitySection/groupActivityIcons'; import {getGroupActivityItem} from 'sentry/views/issueDetails/activitySection/groupActivityItem'; // Only include activity items that describe issue progress changes. Other @@ -77,15 +80,13 @@ function ProgressActivityItem({group, item}: {group: Group; item: GroupActivity} ); const iconMapping = groupActivityTypeIconMapping[item.type]; - const componentFunction = - item.type === GroupActivityType.NOTE ? undefined : iconMapping?.componentFunction; - const Icon = componentFunction - ? componentFunction({ - data: item.data, - user: item.user, - sentry_app: item.sentry_app, - }) - : (iconMapping?.Component ?? null); + // For NOTE activities this component does not use the componentFunction (which + // would create inline avatar components during render). Fall back to the static + // Component (IconChat) instead. + const effectiveMapping = + item.type === GroupActivityType.NOTE && iconMapping + ? {...iconMapping, componentFunction: undefined} + : iconMapping; return ( } marker={} colorConfig={colorConfig} - icon={ - Icon && ( - - ) - } + icon={renderActivityIcon(effectiveMapping, item, {size: 'xs'})} > {typeof message === 'string' ? (