Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref(issue-details): Allows replays section to reflect filters #81128

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {duration} from 'moment-timezone';
import {EventsStatsFixture} from 'sentry-fixture/events';
import {GroupFixture} from 'sentry-fixture/group';
import {ProjectFixture} from 'sentry-fixture/project';
import {RRWebInitFrameEventsFixture} from 'sentry-fixture/replay/rrweb';
import {ReplayListFixture} from 'sentry-fixture/replayList';
import {ReplayRecordFixture} from 'sentry-fixture/replayRecord';
import {TagsFixture} from 'sentry-fixture/tags';

import {initializeOrg} from 'sentry-test/initializeOrg';
import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
Expand Down Expand Up @@ -110,6 +112,15 @@ describe('GroupReplays', () => {
url: `/organizations/org-slug/issues/${mockGroup.id}/`,
body: mockGroup,
});
MockApiClient.addMockResponse({
url: `/organizations/org-slug/issues/${mockGroup.id}/tags/`,
body: TagsFixture(),
method: 'GET',
});
MockApiClient.addMockResponse({
url: `/organizations/org-slug/events-stats/`,
body: {'count()': EventsStatsFixture()},
});
});
afterEach(() => {
resetMockDate();
Expand Down
20 changes: 12 additions & 8 deletions static/app/views/issueDetails/groupReplays/groupReplays.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ import type {Organization} from 'sentry/types/organization';
import {trackAnalytics} from 'sentry/utils/analytics';
import {browserHistory} from 'sentry/utils/browserHistory';
import type EventView from 'sentry/utils/discover/eventView';
import useReplayCountForIssues from 'sentry/utils/replayCount/useReplayCountForIssues';
import useReplayList from 'sentry/utils/replays/hooks/useReplayList';
import useReplayReader from 'sentry/utils/replays/hooks/useReplayReader';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import useUrlParams from 'sentry/utils/useUrlParams';
import {useIssueDetailsEventCount} from 'sentry/views/issueDetails/streamline/useIssueDetailsEventCount';
import {
type ReplayCount,
useIssueDetailsReplayCount,
} from 'sentry/views/issueDetails/streamline/useIssueDetailsReplayCount';
import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';
import useAllMobileProj from 'sentry/views/replays/detail/useAllMobileProj';
import ReplayTable from 'sentry/views/replays/replayTable';
Expand Down Expand Up @@ -177,9 +181,6 @@ function GroupReplaysTable({
}) {
const location = useLocation();
const urlParams = useUrlParams();
const {getReplayCountForIssue} = useReplayCountForIssues({
statsPeriod: '90d',
});
const hasStreamlinedUI = useHasStreamlinedUI();

const replayListData = useReplayList({
Expand All @@ -206,10 +207,13 @@ function GroupReplaysTable({
},
[location]
);
const eventCount = useIssueDetailsEventCount({group});

const selectedReplay = replays?.[selectedReplayIndex];
const {data: replayData} = useIssueDetailsReplayCount<ReplayCount>({group});

const replayCount = replayData?.[group.id] ?? 0;

const replayCount = getReplayCountForIssue(group.id, group.issueCategory);
const nextReplay = replays?.[selectedReplayIndex + 1];
const nextReplayText = nextReplay?.id
? `${nextReplay.user.display_name || t('Anonymous User')}`
Expand Down Expand Up @@ -271,16 +275,16 @@ function GroupReplaysTable({
<StyledLayoutPage withPadding hasStreamlinedUI={hasStreamlinedUI}>
<ReplayCountHeader>
<IconUser size="sm" />
{replayCount ?? 0 > 50
{(replayCount ?? 0) > 50
? tn(
'There are 50+ replays for this issue across %s event',
'There are 50+ replays for this issue across %s events',
group.count
eventCount
)
: t(
'There %s for this issue across %s.',
tn('is %s replay', 'are %s replays', replayCount ?? 0),
tn('%s event', '%s events', group.count)
tn('%s event', '%s events', eventCount)
)}
</ReplayCountHeader>
{inner}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import {decodeScalar} from 'sentry/utils/queryString';
import {DEFAULT_SORT} from 'sentry/utils/replays/fetchReplayList';
import useApi from 'sentry/utils/useApi';
import useCleanQueryParamsOnRouteLeave from 'sentry/utils/useCleanQueryParamsOnRouteLeave';
import {useEventQuery} from 'sentry/views/issueDetails/streamline/eventSearch';
import {useIssueDetailsEventView} from 'sentry/views/issueDetails/streamline/useIssueDetailsDiscoverQuery';
import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';
import {REPLAY_LIST_FIELDS} from 'sentry/views/replays/types';

export default function useReplaysFromIssue({
Expand All @@ -22,6 +25,7 @@ export default function useReplaysFromIssue({
organization: Organization;
}) {
const api = useApi();
const hasStreamlinedUI = useHasStreamlinedUI();

const [replayIds, setReplayIds] = useState<string[]>();

Expand Down Expand Up @@ -53,6 +57,19 @@ export default function useReplaysFromIssue({
}
}, [api, organization.slug, group.id, dataSource, location.query.environment]);

const searchQuery = useEventQuery({group});
const replayQuery = replayIds?.length ? `id:[${String(replayIds)}]` : '';
const combinedQuery = [searchQuery, replayQuery].join(' ').trim();
const issueDetailsEventView = useIssueDetailsEventView({
group,
queryProps: {
version: 2,
fields: REPLAY_LIST_FIELDS,
orderby: decodeScalar(location.query.sort, DEFAULT_SORT),
},
});
issueDetailsEventView.query = combinedQuery;

const eventView = useMemo(() => {
if (!replayIds || !replayIds.length) {
return null;
Expand All @@ -78,7 +95,7 @@ export default function useReplaysFromIssue({
}, [fetchReplayIds]);

return {
eventView,
eventView: hasStreamlinedUI ? issueDetailsEventView : eventView,
fetchError,
isFetching: replayIds === undefined,
pageLinks: null,
Expand Down
15 changes: 9 additions & 6 deletions static/app/views/issueDetails/streamline/eventNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {SavedQueryDatasets} from 'sentry/utils/discover/types';
import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
import parseLinkHeader from 'sentry/utils/parseLinkHeader';
import {keepPreviousData, useApiQuery} from 'sentry/utils/queryClient';
import useReplayCountForIssues from 'sentry/utils/replayCount/useReplayCountForIssues';
import normalizeUrl from 'sentry/utils/url/normalizeUrl';
import {useLocation} from 'sentry/utils/useLocation';
import useMedia from 'sentry/utils/useMedia';
Expand All @@ -29,6 +28,11 @@ import {useParams} from 'sentry/utils/useParams';
import {hasDatasetSelector} from 'sentry/views/dashboards/utils';
import {useGroupEventAttachments} from 'sentry/views/issueDetails/groupEventAttachments/useGroupEventAttachments';
import {useIssueDetailsEventView} from 'sentry/views/issueDetails/streamline/useIssueDetailsDiscoverQuery';
import {useIssueDetailsEventCount} from 'sentry/views/issueDetails/streamline/useIssueDetailsEventCount';
import {
type ReplayCount,
useIssueDetailsReplayCount,
} from 'sentry/views/issueDetails/streamline/useIssueDetailsReplayCount';
import {Tab, TabPaths} from 'sentry/views/issueDetails/types';
import {useGroupDetailsRoute} from 'sentry/views/issueDetails/useGroupDetailsRoute';
import {
Expand Down Expand Up @@ -136,11 +140,10 @@ export function IssueEventNavigation({event, group, query}: IssueEventNavigation
notifyOnChangeProps: [],
}
);
const eventCount = useIssueDetailsEventCount({group});

const {getReplayCountForIssue} = useReplayCountForIssues({
statsPeriod: '90d',
});
const replaysCount = getReplayCountForIssue(group.id, group.issueCategory) ?? 0;
const {data: replayData} = useIssueDetailsReplayCount<ReplayCount>({group});
const replaysCount = replayData?.[group.id] ?? 0;

const attachments = useGroupEventAttachments({
groupId: group.id,
Expand Down Expand Up @@ -201,7 +204,7 @@ export function IssueEventNavigation({event, group, query}: IssueEventNavigation
key: Tab.DETAILS,
label: (
<DropdownCountWrapper isCurrentTab={currentTab === Tab.DETAILS}>
{TabName[Tab.DETAILS]} <ItemCount value={group.count} />
{TabName[Tab.DETAILS]} <ItemCount value={eventCount} />
</DropdownCountWrapper>
),
textValue: TabName[Tab.DETAILS],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {useMemo} from 'react';

import type {Group} from 'sentry/types/group';
import type {MultiSeriesEventsStats} from 'sentry/types/organization';
import {
useIssueDetailsDiscoverQuery,
useIssueDetailsEventView,
} from 'sentry/views/issueDetails/streamline/useIssueDetailsDiscoverQuery';

export function useIssueDetailsEventCount({group}: {group: Group}) {
const eventView = useIssueDetailsEventView({group});
const {data: groupStats} = useIssueDetailsDiscoverQuery<MultiSeriesEventsStats>({
params: {
route: 'events-stats',
eventView,
referrer: 'issue_details.streamline_graph',
},
});
const eventCount = useMemo(() => {
if (!groupStats?.['count()']) {
return 0;
}
return groupStats['count()']?.data?.reduce((count, [_timestamp, countData]) => {
return count + (countData?.[0]?.count ?? 0);
}, 0);
}, [groupStats]);
return eventCount;
}
Comment on lines +10 to +28
Copy link
Member Author

@leeandher leeandher Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kinda sucks it's the entire query for a timeseries that I iterate over to create a count.

There is definitely an easier way, but I couldn't figure out the discover syntax to get there.

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {type Group, IssueCategory} from 'sentry/types/group';
import {
type ApiQueryKey,
useApiQuery,
type UseApiQueryOptions,
} from 'sentry/utils/queryClient';
import useOrganization from 'sentry/utils/useOrganization';
import {useEventQuery} from 'sentry/views/issueDetails/streamline/eventSearch';
import {useIssueDetailsEventView} from 'sentry/views/issueDetails/streamline/useIssueDetailsDiscoverQuery';

interface ReplayIdsParameters {
orgSlug: string;
query: {
data_source: 'discover' | 'search_issues';
query: string;
statsPeriod: string;
environment?: string[];
returnIds?: boolean;
};
}

export type ReplayCount = Record<string, number>;
export type ReplayIds = Record<string, string[]>;

function makeReplayCountQueryKey({orgSlug, query}: ReplayIdsParameters): ApiQueryKey {
return [`/organizations/${orgSlug}/replay-count/`, {query}];
}

function useReplayCount<T>(
params: ReplayIdsParameters,
options: Partial<UseApiQueryOptions<T>> = {}
) {
return useApiQuery<T>(makeReplayCountQueryKey(params), {
staleTime: Infinity,
retry: false,
...options,
});
}

export function useIssueDetailsReplayCount<T extends ReplayCount | ReplayIds>({
group,
}: {
group: Group;
}) {
const organization = useOrganization();
const searchQuery = useEventQuery({group});
const eventView = useIssueDetailsEventView({group});
return useReplayCount<T>({
orgSlug: organization.slug,
query: {
data_source:
group.issueCategory === IssueCategory.ERROR ? 'discover' : 'search_issues',
statsPeriod: eventView.statsPeriod ?? '90d',
environment: [...eventView.environment],
query: `issue.id:[${group.id}] ${searchQuery}`,
},
});
}
Loading