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 14 commits into
base: master
Choose a base branch
from
Draft
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
18 changes: 11 additions & 7 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/hooks/useIssueDetailsEventCount';
import {
type ReplayCount,
useIssueDetailsReplayCount,
} from 'sentry/views/issueDetails/streamline/hooks/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 @@ -275,12 +279,12 @@ function GroupReplaysTable({
? 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/hooks/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
19 changes: 12 additions & 7 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/hooks/useIssueDetailsDiscoverQuery';
import {useIssueDetailsEventCount} from 'sentry/views/issueDetails/streamline/hooks/useIssueDetailsEventCount';
import {
type ReplayCount,
useIssueDetailsReplayCount,
} from 'sentry/views/issueDetails/streamline/hooks/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({
group,
Expand Down Expand Up @@ -207,7 +210,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 Expand Up @@ -241,7 +244,9 @@ export function IssueEventNavigation({event, group, query}: IssueEventNavigation
<DropdownCountWrapper isCurrentTab={currentTab === Tab.ATTACHMENTS}>
{TabName[Tab.ATTACHMENTS]}
<CustomItemCount>
{hasManyAttachments ? '50+' : attachments.attachments.length}
{hasManyAttachments
? `${attachments.attachments.length}+`
: attachments.attachments.length}
</CustomItemCount>
</DropdownCountWrapper>
),
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/hooks/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;
}
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/hooks/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