Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,96 @@ describe('AttributesTreeValue', () => {

expect(screen.getByText('null')).toBeInTheDocument();
});

it('renders JSON object values as structured data', () => {
const jsonContent = {
...defaultProps.content,
value: '{"key": "value", "number": 42}',
};

render(<AttributesTreeValue {...defaultProps} content={jsonContent} />);

expect(screen.getByText('key')).toBeInTheDocument();
expect(screen.getByText('value')).toBeInTheDocument();
expect(screen.getByText('42')).toBeInTheDocument();
});

it('renders JSON array values as structured data', () => {
const jsonContent = {
...defaultProps.content,
value: '[1, 2, 3]',
};

render(<AttributesTreeValue {...defaultProps} content={jsonContent} />);

expect(screen.getByText('1')).toBeInTheDocument();
expect(screen.getByText('2')).toBeInTheDocument();
expect(screen.getByText('3')).toBeInTheDocument();
});

it('renders invalid JSON containing braces as plain text', () => {
const invalidJsonContent = {
...defaultProps.content,
value: 'not {json',
};

render(<AttributesTreeValue {...defaultProps} content={invalidJsonContent} />);

expect(screen.getByText('not {json')).toBeInTheDocument();
});

it('renders plain strings without braces as plain text', () => {
const plainContent = {
...defaultProps.content,
value: 'hello world',
};

render(<AttributesTreeValue {...defaultProps} content={plainContent} />);

expect(screen.getByText('hello world')).toBeInTheDocument();
});

it('does not render JSON as structured data when disableRichValue is true', () => {
const jsonContent = {
...defaultProps.content,
value: '{"key": "value"}',
};

render(
<AttributesTreeValue
{...defaultProps}
content={jsonContent}
config={{disableRichValue: true}}
/>
);

expect(screen.getByText('{"key": "value"}')).toBeInTheDocument();
expect(screen.queryByRole('list')).not.toBeInTheDocument();
});

it('renders simple JSON with compact class', () => {
const jsonContent = {
...defaultProps.content,
value: '{"boop": "bop"}',
};

render(<AttributesTreeValue {...defaultProps} content={jsonContent} />);

const pre = screen.getByText('bop').closest('pre');
expect(pre).toHaveClass('compact');
});

it('renders long JSON without compact class', () => {
const jsonContent = {
...defaultProps.content,
value: '{"k": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}',
};

render(<AttributesTreeValue {...defaultProps} content={jsonContent} />);

const pre = screen
.getByText('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
.closest('pre');
expect(pre).not.toHaveClass('compact');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import styled from '@emotion/styled';

import {openNavigateToExternalLinkModal} from 'sentry/actionCreators/modal';
import {ExternalLink} from 'sentry/components/links/externalLink';
import {StructuredEventData} from 'sentry/components/structuredEventData';
import {type RenderFunctionBaggage} from 'sentry/utils/discover/fieldRenderers';
import {isUrl} from 'sentry/utils/string/isUrl';
import {AnnotatedAttributeTooltip} from 'sentry/views/explore/components/annotatedAttributeTooltip';
import {getAttributeItem} from 'sentry/views/explore/components/traceItemAttributes/utils';
import {TraceItemMetaInfo} from 'sentry/views/explore/utils';
import {tryParseJson} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/utils';

import type {
AttributesFieldRender,
Expand All @@ -32,11 +34,12 @@ export function AttributesTreeValue<RendererExtra extends RenderFunctionBaggage>
// Check if we have a custom renderer for this attribute
const attributeKey = originalAttribute.original_attribute_key;
const renderer = renderers[attributeKey];
const value = String(content.value);

const defaultValue = <span>{String(content.value)}</span>;
const defaultValue = <span>{value}</span>;

if (config?.disableRichValue) {
return String(content.value);
return value;
}

if (renderer) {
Expand All @@ -57,12 +60,24 @@ export function AttributesTreeValue<RendererExtra extends RenderFunctionBaggage>
);
}
}
return isUrl(String(content.value)) ? (
const parsedJson = tryParseJson(content.value);
Comment thread
cursor[bot] marked this conversation as resolved.
if (typeof parsedJson === 'object') {
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
return (
<AttributeStructuredData
data={parsedJson}
maxDefaultDepth={2}
withAnnotatedText={false}
className={value.length <= 48 ? 'compact' : undefined}
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
/>
);
Comment thread
sentry[bot] marked this conversation as resolved.
}

return isUrl(value) ? (
<AttributeLinkText>
<ExternalLink
onClick={e => {
e.preventDefault();
openNavigateToExternalLinkModal({linkText: String(content.value)});
openNavigateToExternalLinkModal({linkText: value});
}}
>
{defaultValue}
Expand All @@ -86,3 +101,29 @@ const AttributeLinkText = styled('span')`
white-space: normal;
}
`;

const AttributeStructuredData = styled(StructuredEventData)`
margin: 0;
padding: 0;
background: transparent;
white-space: pre-wrap;
word-break: break-word;

&.compact {
display: inline;

span[data-base-with-toggle='true'] {
Comment thread
JoshuaKGoldberg marked this conversation as resolved.
display: inline;
padding-left: 0;
}

button {
display: none;
}

div {
display: inline;
padding-left: 0;
}
}
`;
Loading