123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- import {Fragment, useMemo, useState} from 'react';
- import styled from '@emotion/styled';
- import startCase from 'lodash/startCase';
- import moment from 'moment';
- import {Button} from 'sentry/components/button';
- import KeyValueList from 'sentry/components/events/interfaces/keyValueList';
- import {AnnotatedText} from 'sentry/components/events/meta/annotatedText';
- import ExternalLink from 'sentry/components/links/externalLink';
- import ListItem from 'sentry/components/list/listItem';
- import {JavascriptProcessingErrors} from 'sentry/constants/eventErrors';
- import {t, tct} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- export type EventErrorData = {
- message: React.ReactNode;
- type: string;
- data?: {
- image_name?: string;
- image_path?: string;
- message?: string;
- name?: string;
- sdk_time?: string;
- server_time?: string;
- url?: string;
- } & Record<string, any>;
- };
- const keyMapping = {
- image_uuid: 'Debug ID',
- image_name: 'File Name',
- image_path: 'File Path',
- };
- export type ErrorItemProps = {
- error: EventErrorData;
- meta?: Record<any, any>;
- };
- export function ErrorItem({error, meta}: ErrorItemProps) {
- const [expanded, setExpanded] = useState(false);
- const cleanedData = useMemo(() => {
- const data = {...(error.data ?? {})};
- // The name is rendered as path in front of the message
- if (typeof data.name === 'string') {
- delete data.name;
- }
- if (data.message === 'None') {
- // Python ensures a message string, but "None" doesn't make sense here
- delete data.message;
- }
- if (typeof data.image_path === 'string') {
- // Separate the image name for readability
- const separator = /^([a-z]:\\|\\\\)/i.test(data.image_path) ? '\\' : '/';
- const path = data.image_path.split(separator);
- data.image_name = path.splice(-1, 1)[0];
- data.image_path = path.length ? path.join(separator) + separator : '';
- }
- if (typeof data.server_time === 'string' && typeof data.sdk_time === 'string') {
- data.message = t(
- 'Adjusted timestamps by %s',
- moment
- .duration(moment.utc(data.server_time).diff(moment.utc(data.sdk_time)))
- .humanize()
- );
- }
- return Object.entries(data)
- .map(([key, value]) => ({
- key,
- value,
- subject: keyMapping[key] || startCase(key),
- meta: key === 'image_name' ? meta?.image_path?.[''] : meta?.[key]?.[''],
- }))
- .filter(d => {
- if (!d.value && !!d.meta) {
- return true;
- }
- return !!d.value;
- });
- }, [error.data, meta]);
- return (
- <StyledListItem data-test-id="event-error-item">
- <OverallInfo>
- <div>
- {meta?.data?.name?.[''] ? (
- <AnnotatedText value={error.message} meta={meta?.data?.name?.['']} />
- ) : !error.data?.name || typeof error.data?.name !== 'string' ? null : (
- <Fragment>
- <strong>{error.data?.name}</strong>
- {': '}
- </Fragment>
- )}
- {meta?.message?.[''] ? (
- <AnnotatedText value={error.message} meta={meta?.message?.['']} />
- ) : (
- error.message
- )}
- {Object.values(JavascriptProcessingErrors).includes(
- error.type as JavascriptProcessingErrors
- ) && (
- <Fragment>
- {' '}
- (
- {tct('see [docsLink]', {
- docsLink: (
- <StyledExternalLink href="https://docs.sentry.io/platforms/javascript/sourcemaps/troubleshooting_js/">
- {t('Troubleshooting for JavaScript')}
- </StyledExternalLink>
- ),
- })}
- )
- </Fragment>
- )}
- </div>
- {!!cleanedData.length && (
- <ToggleButton
- onClick={event => {
- event.stopPropagation();
- setExpanded(!expanded);
- }}
- priority="link"
- size="zero"
- >
- {expanded ? t('Collapse') : t('Expand')}
- </ToggleButton>
- )}
- </OverallInfo>
- {expanded && <KeyValueList data={cleanedData} isContextData />}
- </StyledListItem>
- );
- }
- const ToggleButton = styled(Button)`
- margin-left: ${space(1.5)};
- font-weight: 700;
- color: ${p => p.theme.subText};
- :hover,
- :focus {
- color: ${p => p.theme.textColor};
- }
- `;
- const StyledListItem = styled(ListItem)`
- margin-bottom: ${space(0.75)};
- `;
- const StyledExternalLink = styled(ExternalLink)`
- /* && is here to increase specificity to override default styles*/
- && {
- font-weight: inherit;
- color: inherit;
- text-decoration: underline;
- }
- `;
- const OverallInfo = styled('div')`
- display: grid;
- grid-template-columns: repeat(2, minmax(auto, max-content));
- word-break: break-all;
- `;
|