Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions static/app/views/explore/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export function getExploreUrl({
sort,
field,
id,
table,
title,
referrer,
}: {
Expand All @@ -79,6 +80,7 @@ export function getExploreUrl({
referrer?: string;
selection?: PageFilters;
sort?: string;
table?: string;
title?: string;
visualize?: BaseVisualize[];
}) {
Expand All @@ -100,6 +102,7 @@ export function getExploreUrl({
field,
utc,
id,
table,
title,
referrer,
};
Expand Down
6 changes: 6 additions & 0 deletions static/app/views/performance/newTraceDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from 'sentry/views/performance/newTraceDetails/traceOurlogs';
import {TraceSummarySection} from 'sentry/views/performance/newTraceDetails/traceSummary';
import {TraceTabsAndVitals} from 'sentry/views/performance/newTraceDetails/traceTabsAndVitals';
import {PartialTraceDataWarning} from 'sentry/views/performance/newTraceDetails/traceTypeWarnings/partialTraceDataWarning';
import {TraceWaterfall} from 'sentry/views/performance/newTraceDetails/traceWaterfall';
import {
TraceLayoutTabKeys,
Expand Down Expand Up @@ -165,6 +166,11 @@ function TraceViewImpl({traceSlug}: {traceSlug: string}) {
traceSlug={traceSlug}
organization={organization}
/>
<PartialTraceDataWarning
timestamp={queryParams.timestamp}
logs={logsData}
tree={tree}
/>
<TraceTabsAndVitals
tabsConfig={{
tabOptions,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {OrganizationFixture} from 'sentry-fixture/organization';

import {render, screen} from 'sentry-test/reactTestingLibrary';

import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';
import {
makeEAPSpan,
makeEAPTrace,
} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeTestUtils';

import {PartialTraceDataWarning} from './partialTraceDataWarning';

describe('PartialTraceDataWarning', () => {
describe('when the trace is older than 30 days', () => {
beforeAll(() => {
jest.useFakeTimers().setSystemTime(new Date(2025, 0, 31));
});

afterAll(() => {
jest.useRealTimers();
});

it('should render warning', () => {
const organization = OrganizationFixture();
const start = new Date('2024-01-01T00:00:00Z').getTime() / 1e3;

const eapTrace = makeEAPTrace([
makeEAPSpan({
op: 'http.server',
start_timestamp: start,
end_timestamp: start + 2,
children: [makeEAPSpan({start_timestamp: start + 1, end_timestamp: start + 4})],
}),
]);

render(
<PartialTraceDataWarning
logs={[]}
timestamp={start}
tree={TraceTree.FromTrace(eapTrace, {replay: null, meta: null, organization})}
/>,
{organization}
);

expect(screen.getByText('Partial Trace Data:')).toBeInTheDocument();

expect(
screen.getByText(
'Trace may be missing spans since the age of the trace is older than 30 days'
)
).toBeInTheDocument();

expect(
screen.getByRole('link', {name: 'Search similar traces in the past 24 hours'})
).toBeInTheDocument();

const queryString = encodeURIComponent('is_transaction:true span.op:http.server');
expect(
screen.getByRole('link', {name: 'Search similar traces in the past 24 hours'})
).toHaveAttribute(
'href',
`/organizations/${organization.slug}/explore/traces/?mode=samples&project=1&query=${queryString}&statsPeriod=24h`
);
});
});

describe('when the trace is younger than 30 days', () => {
beforeAll(() => {
jest.useFakeTimers().setSystemTime(new Date(2025, 0, 1));
});

afterAll(() => {
jest.useRealTimers();
});

it('should not render the warning', () => {
const organization = OrganizationFixture();
const start = new Date('2025-01-01T00:00:00Z').getTime() / 1e3;

const eapTrace = makeEAPTrace([
makeEAPSpan({
op: 'http.server',
start_timestamp: start,
end_timestamp: start + 2,
children: [makeEAPSpan({start_timestamp: start + 1, end_timestamp: start + 4})],
}),
]);

render(
<PartialTraceDataWarning
logs={[]}
timestamp={start}
tree={TraceTree.FromTrace(eapTrace, {replay: null, meta: null, organization})}
/>,
{organization}
);

expect(screen.queryByText('Partial Trace Data:')).not.toBeInTheDocument();

expect(
screen.queryByText(
'Trace may be missing spans since the age of the trace is older than 30 days'
)
).not.toBeInTheDocument();

expect(
screen.queryByRole('link', {name: 'Search similar traces in the past 24 hours'})
).not.toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {useMemo} from 'react';
import moment from 'moment-timezone';

import {Alert} from '@sentry/scraps/alert';
import {Link} from '@sentry/scraps/link';
import {Text} from '@sentry/scraps/text';

import {t, tct} from 'sentry/locale';
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
import useOrganization from 'sentry/utils/useOrganization';
import usePageFilters from 'sentry/utils/usePageFilters';
import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode';
import type {OurLogsResponseItem} from 'sentry/views/explore/logs/types';
import {getExploreUrl} from 'sentry/views/explore/utils';
import {getRepresentativeTraceEvent} from 'sentry/views/performance/newTraceDetails/traceApi/utils';
import type {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';

interface PartialTraceDataWarningProps {
logs: OurLogsResponseItem[] | undefined;
timestamp: number | undefined;
tree: TraceTree;
}

export function PartialTraceDataWarning({
logs,
timestamp,
tree,
}: PartialTraceDataWarningProps) {
const organization = useOrganization();
const {selection} = usePageFilters();

const rep = getRepresentativeTraceEvent(tree, logs);

let op = '';
if (rep?.event) {
op =
'transaction.op' in rep.event
? `${rep.event?.['transaction.op']}`
: 'op' in rep.event
? `${rep.event.op}`
: '';
}

const queryString = useMemo(() => {
const search = new MutableSearch('');
search.addFilterValue('is_transaction', 'true');

if (op) {
search.addFilterValue('span.op', op);
}

return search.formatString();
}, []);

Check failure on line 53 in static/app/views/performance/newTraceDetails/traceTypeWarnings/partialTraceDataWarning.tsx

View workflow job for this annotation

GitHub Actions / pre-commit lint

React Hook useMemo has a missing dependency: 'op'. Either include it or remove the dependency array

Check failure on line 53 in static/app/views/performance/newTraceDetails/traceTypeWarnings/partialTraceDataWarning.tsx

View workflow job for this annotation

GitHub Actions / eslint

React Hook useMemo has a missing dependency: 'op'. Either include it or remove the dependency array

if (!timestamp) {
return null;
}

const now = moment();
const isTraceOldEnough = moment(timestamp * 1000).isBefore(now.subtract(30, 'days'));

if (!isTraceOldEnough) {
return null;
}

const projects =
typeof rep.event?.project_id === 'number' ? [rep.event?.project_id] : [];

const exploreUrl = getExploreUrl({
organization,
mode: Mode.SAMPLES,
query: queryString,
selection: {
...selection,
projects,
datetime: {
start: null,
end: null,
utc: null,
period: '24h',
},
},
});

return (
<Alert
type="warning"
trailingItems={
<Link to={exploreUrl}>{t('Search similar traces in the past 24 hours')}</Link>
}
>
<Text as="p">
{tct(
'[dataCategory] Trace may be missing spans since the age of the trace is older than 30 days',
{
dataCategory: <Text bold>{t('Partial Trace Data:')}</Text>,
}
)}
</Text>
</Alert>
);
}
Loading