import {Fragment, memo} from 'react'; import styled from '@emotion/styled'; import isNil from 'lodash/isNil'; import Access from 'sentry/components/acl/access'; import Button from 'sentry/components/button'; import DebugFileFeature from 'sentry/components/debugFileFeature'; import {formatAddress, getImageRange} from 'sentry/components/events/interfaces/utils'; import {PanelItem} from 'sentry/components/panels'; import Tooltip from 'sentry/components/tooltip'; import {IconCheckmark, IconCircle, IconFlag, IconSearch} from 'sentry/icons'; import {t} from 'sentry/locale'; import space from 'sentry/styles/space'; import {Organization, Project} from 'sentry/types'; import {DebugImage as DebugImageType, DebugStatus} from './types'; import {combineStatus, getFileName} from './utils'; type Status = ReturnType; const IMAGE_ADDR_LEN = 12; function getImageStatusText(status: Status) { switch (status) { case 'found': return t('ok'); case 'unused': return t('unused'); case 'missing': return t('missing'); case 'malformed': case 'fetching_failed': case 'timeout': case 'other': return t('failed'); default: return null; } } function getImageStatusDetails(status: Status) { switch (status) { case 'found': return t('Debug information for this image was found and successfully processed.'); case 'unused': return t('The image was not required for processing the stack trace.'); case 'missing': return t('No debug information could be found in any of the specified sources.'); case 'malformed': return t('The debug information file for this image failed to process.'); case 'timeout': case 'fetching_failed': return t('The debug information file for this image could not be downloaded.'); case 'other': return t('An internal error occurred while handling this image.'); default: return null; } } type Props = { image: DebugImageType; organization: Organization; projectId: Project['id']; showDetails: boolean; style?: React.CSSProperties; }; const DebugImage = memo(({image, organization, projectId, showDetails, style}: Props) => { const orgSlug = organization.slug; const getSettingsLink = () => { if (!orgSlug || !projectId || !image.debug_id) { return null; } return `/settings/${orgSlug}/projects/${projectId}/debug-symbols/?query=${image.debug_id}`; }; const renderStatus = (title: string, status: DebugStatus) => { if (isNil(status)) { return null; } const text = getImageStatusText(status); if (!text) { return null; } return ( {title}: {text} ); }; const combinedStatus = combineStatus(image.debug_status, image.unwind_status); const [startAddress, endAddress] = getImageRange(image); const renderIconElement = () => { switch (combinedStatus) { case 'unused': return ( ); case 'found': return ( ); default: return ( ); } }; const codeFile = getFileName(image.code_file); const debugFile = image.debug_file && getFileName(image.debug_file); // The debug file is only realistically set on Windows. All other platforms // either leave it empty or set it to a filename that's equal to the code // file name. In this case, do not show it. const showDebugFile = debugFile && codeFile !== debugFile; // Availability only makes sense if the image is actually referenced. // Otherwise, the processing pipeline does not resolve this kind of // information and it will always be false. const showAvailability = !isNil(image.features) && combinedStatus !== 'unused'; // The code id is sometimes missing, and sometimes set to the equivalent of // the debug id (e.g. for Mach symbols). In this case, it is redundant // information and we do not want to show it. const showCodeId = !!image.code_id && image.code_id !== image.debug_id; // Old versions of the event pipeline did not store the symbolication // status. In this case, default to display the debug_id instead of stack // unwind information. const legacyRender = isNil(image.debug_status); const debugIdElement = ( {t('Debug ID')}: {image.debug_id} ); const formattedImageStartAddress = startAddress ? ( {formatAddress(startAddress, IMAGE_ADDR_LEN)} ) : null; const formattedImageEndAddress = endAddress ? ( {formatAddress(endAddress, IMAGE_ADDR_LEN)} ) : null; return ( {renderIconElement()} {startAddress && endAddress ? ( {formattedImageStartAddress} {' \u2013 '} {formattedImageEndAddress} ) : null} {codeFile} {showDebugFile && ({debugFile})} {legacyRender ? ( debugIdElement ) : ( {renderStatus(t('Stack Unwinding'), image.unwind_status)} {renderStatus(t('Symbolication'), image.debug_status)} )} {showDetails && ( {showAvailability && ( {t('Availability')}: )} {!legacyRender && debugIdElement} {showCodeId && ( {t('Code ID')}:{' '} {image.code_id} )} {!!image.arch && ( {t('Architecture')}: {image.arch} )} )} {({hasAccess}) => { if (!hasAccess) { return null; } const settingsUrl = getSettingsLink(); if (!settingsUrl) { return null; } return (