Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {Text} from '@sentry/scraps/text';
import {Tooltip} from '@sentry/scraps/tooltip';

import {Count} from 'sentry/components/count';
import {usePageFilters} from 'sentry/components/pageFilters/usePageFilters';
import {
COL_WIDTH_UNDEFINED,
GridEditable,
Expand Down Expand Up @@ -35,14 +36,26 @@ import {hasGenAiConversationsFeature} from 'sentry/views/explore/conversations/u
import {LLMCosts} from 'sentry/views/insights/pages/agents/components/llmCosts';
import {AIContentRenderer} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/span/eapSections/aiContentRenderer';

function getConversationDetailUrl(orgSlug: string, conversation: Conversation): string {
const ONE_HOUR_MS = 60 * 60 * 1000;

function getConversationDetailUrl(
orgSlug: string,
conversation: Conversation,
projects: number[]
): string {
const basePath = `/organizations/${orgSlug}/explore/${CONVERSATIONS_LANDING_SUB_PATH}/${encodeURIComponent(conversation.conversationId)}/`;
const params = new URLSearchParams();
if (conversation.startTimestamp) {
params.set('start', new Date(conversation.startTimestamp).toISOString());
params.set(
'start',
new Date(conversation.startTimestamp - ONE_HOUR_MS).toISOString()
);
}
if (conversation.endTimestamp) {
params.set('end', new Date(conversation.endTimestamp).toISOString());
params.set('end', new Date(conversation.endTimestamp + ONE_HOUR_MS).toISOString());
}
for (const project of projects) {
params.append('project', String(project));
}
const qs = params.toString();
return normalizeUrl(qs ? `${basePath}?${qs}` : basePath);
Expand Down Expand Up @@ -205,15 +218,18 @@ const BodyCell = memo(function BodyCell({
}) {
const organization = useOrganization();
const navigate = useNavigate();
const {selection} = usePageFilters();

const navigateToDetail = useCallback(() => {
navigate(getConversationDetailUrl(organization.slug, dataRow));
}, [navigate, organization.slug, dataRow]);
navigate(getConversationDetailUrl(organization.slug, dataRow, selection.projects));
}, [navigate, organization.slug, dataRow, selection.projects]);

switch (column.key) {
case 'conversationId':
return (
<ConversationIdLink to={getConversationDetailUrl(organization.slug, dataRow)}>
<ConversationIdLink
to={getConversationDetailUrl(organization.slug, dataRow, selection.projects)}
>
{isUUID(dataRow.conversationId) ? (
dataRow.conversationId.slice(0, 8)
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,14 +243,14 @@ describe('useConversation', () => {
});

// Verify the API was called with correct timestamps (with 1-hour padding)
// and that project comes from page filters (empty array = my projects), not hardcoded -1
// and ALL_ACCESS_PROJECTS (-1) when no project is selected in page filters
expect(mockRequest).toHaveBeenCalledWith(
expect.stringContaining('/ai-conversations/conv-timestamps/'),
expect.objectContaining({
query: expect.objectContaining({
start: new Date(startTimestamp - 60 * 60 * 1000).toISOString(),
end: new Date(endTimestamp + 60 * 60 * 1000).toISOString(),
project: [],
project: [-1],
}),
})
);
Expand Down Expand Up @@ -403,6 +403,65 @@ describe('useConversation', () => {
expect(queryArg).not.toHaveProperty('statsPeriod');
});

it('falls back to ALL_ACCESS_PROJECTS and 30d when no filters are set', async () => {
const mockRequest = MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/ai-conversations/conv-123/`,
body: [BASE_SPAN],
});

const {result} = renderHookWithProviders(
() => useConversation({conversationId: 'conv-123'}),
{organization}
);

await waitFor(() => expect(result.current.isLoading).toBe(false));

expect(mockRequest).toHaveBeenCalledWith(
expect.stringContaining('/ai-conversations/conv-123/'),
expect.objectContaining({
query: expect.objectContaining({
project: [-1],
statsPeriod: '30d',
}),
})
);
});

it('uses relative period from page filters when explicitly set', async () => {
act(() =>
PageFiltersStore.updateDateTime({
period: '7d',
start: null,
end: null,
utc: null,
})
);

const mockRequest = MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/ai-conversations/conv-123/`,
body: [BASE_SPAN],
});

const {result} = renderHookWithProviders(
() => useConversation({conversationId: 'conv-123'}),
{organization}
);

await waitFor(() => expect(result.current.isLoading).toBe(false));

expect(mockRequest).toHaveBeenCalledWith(
expect.stringContaining('/ai-conversations/conv-123/'),
expect.objectContaining({
query: expect.objectContaining({
statsPeriod: '7d',
}),
})
);
const queryArg = mockRequest.mock.calls[0]![1]!.query;
expect(queryArg).not.toHaveProperty('start');
expect(queryArg).not.toHaveProperty('end');
});

it('filters to only gen_ai spans', async () => {
MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/ai-conversations/conv-filter/`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import {useEffect, useMemo} from 'react';
import {skipToken, useInfiniteQuery} from '@tanstack/react-query';

import {
ALL_ACCESS_PROJECTS,
getDefaultPageFilterSelection,
} from 'sentry/components/pageFilters/constants';
import {normalizeDateTimeParams} from 'sentry/components/pageFilters/parse';
import {usePageFilters} from 'sentry/components/pageFilters/usePageFilters';
import {apiOptions} from 'sentry/utils/api/apiOptions';
Expand Down Expand Up @@ -186,15 +190,25 @@ export function useConversation(
const hasConversationTimestamps =
conversation.startTimestamp !== undefined && conversation.endTimestamp !== undefined;

const defaultPeriod = getDefaultPageFilterSelection().datetime.period;
const hasExplicitDatetime =
selection.datetime.start !== null ||
(selection.datetime.period !== null && selection.datetime.period !== defaultPeriod);
Comment thread
obostjancic marked this conversation as resolved.

const datetimeParams = hasConversationTimestamps
? {
start: new Date(conversation.startTimestamp! - ONE_HOUR_MS).toISOString(),
end: new Date(conversation.endTimestamp! + ONE_HOUR_MS).toISOString(),
}
: normalizeDateTimeParams(selection.datetime);
: hasExplicitDatetime
? normalizeDateTimeParams(selection.datetime)
: {statsPeriod: '30d'};
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
obostjancic marked this conversation as resolved.

const project =
selection.projects.length > 0 ? selection.projects : [ALL_ACCESS_PROJECTS];

const queryParams = {
project: selection.projects,
project,
per_page: 1000,
...datetimeParams,
};
Expand Down
3 changes: 3 additions & 0 deletions static/app/views/explore/conversations/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ function ConversationsLayout() {

function ConversationsLayoutContent() {
const organization = useOrganization();
const {conversationId} = useParams<{conversationId?: string}>();
const isDetailPage = !!conversationId;

return (
<SentryDocumentTitle title={CONVERSATIONS_LANDING_TITLE} orgSlug={organization.slug}>
Expand All @@ -55,6 +57,7 @@ function ConversationsLayoutContent() {
<PageFiltersContainer
maxPickableDays={MAX_PICKABLE_DAYS}
storageNamespace="conversations"
skipLoadLastUsed={isDetailPage}
>
<Outlet />
</PageFiltersContainer>
Expand Down
Loading