Skip to content
24 changes: 10 additions & 14 deletions static/app/components/feedback/list/feedbackList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Fragment, useMemo} from 'react';
import {useMemo} from 'react';
import styled from '@emotion/styled';
import {useInfiniteQuery} from '@tanstack/react-query';
import uniqBy from 'lodash/uniqBy';
Expand All @@ -17,8 +17,9 @@ import {InfiniteListState} from 'sentry/components/infiniteList/infiniteListStat
import {LoadingIndicator} from 'sentry/components/loadingIndicator';
import {t} from 'sentry/locale';
import type {ApiResponse} from 'sentry/utils/api/apiFetch';
import {safeParseQueryKey} from 'sentry/utils/api/apiQueryKey';
import type {FeedbackIssueListItem} from 'sentry/utils/feedback/types';
import {useListItemCheckboxContext} from 'sentry/utils/list/useListItemCheckboxState';
import {ListItemCheckboxProvider} from 'sentry/utils/list/useListItemCheckboxState';

function NoFeedback() {
return (
Expand All @@ -43,15 +44,14 @@ export function FeedbackList({onItemSelect}: Props) {
() => uniqBy(queryResult.data?.pages.flatMap(page => page.json) ?? [], 'id'),
[queryResult.data?.pages]
);
const checkboxState = useListItemCheckboxContext({
hits: Number(queryResult.data?.pages[0]?.headers['X-Hits'] ?? issues.length),
knownIds: issues.map(issue => issue.id),
queryKey: listApiOptions.queryKey,
});

return (
<Fragment>
<FeedbackListHeader {...checkboxState} />
<ListItemCheckboxProvider
hits={Number(queryResult.data?.pages[0]?.headers['X-Hits'] ?? issues.length)}
knownIds={issues.map(issue => issue.id)}
endpointOptions={safeParseQueryKey(listApiOptions.queryKey)?.options}
>
<FeedbackListHeader />
<Stack flexGrow={1} paddingBottom="xs">
<InfiniteListState
queryResult={queryResult}
Expand All @@ -73,10 +73,6 @@ export function FeedbackList({onItemSelect}: Props) {
<ErrorBoundary mini>
<FeedbackListItem
feedbackItem={item}
isSelected={checkboxState.isSelected(item.id)}
onSelect={() => {
checkboxState.toggleSelected(item.id);
}}
onItemSelect={() => onItemSelect(itemIndex)}
/>
</ErrorBoundary>
Expand All @@ -94,7 +90,7 @@ export function FeedbackList({onItemSelect}: Props) {
/>
</InfiniteListState>
</Stack>
</Fragment>
</ListItemCheckboxProvider>
);
}

Expand Down
29 changes: 10 additions & 19 deletions static/app/components/feedback/list/feedbackListHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,17 @@ import {useFeedbackHasNewItems} from 'sentry/components/feedback/useFeedbackHasN
import {useMailbox} from 'sentry/components/feedback/useMailbox';
import {IconRefresh} from 'sentry/icons';
import {t} from 'sentry/locale';
import type {ListItemCheckboxState} from 'sentry/utils/list/useListItemCheckboxState';
import {useListItemCheckboxContext} from 'sentry/utils/list/useListItemCheckboxState';

interface Props extends Pick<
ListItemCheckboxState,
| 'countSelected'
| 'deselectAll'
| 'isAllSelected'
| 'isAnySelected'
| 'selectAll'
| 'selectedIds'
> {}

export function FeedbackListHeader({
countSelected,
deselectAll,
isAllSelected,
isAnySelected,
selectAll,
selectedIds,
}: Props) {
export function FeedbackListHeader() {
const {
countSelected,
deselectAll,
isAllSelected,
isAnySelected,
selectAll,
selectedIds,
} = useListItemCheckboxContext();
const [mailbox, setMailbox] = useMailbox();

const {listPrefetchApiOptions, resetListHeadTime} = useFeedbackApiOptions();
Expand Down
20 changes: 8 additions & 12 deletions static/app/components/feedback/list/feedbackListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {Group} from 'sentry/types/group';
import {trackAnalytics} from 'sentry/utils/analytics';
import {feedbackHasLinkedError} from 'sentry/utils/feedback/hasLinkedError';
import {type FeedbackIssueListItem} from 'sentry/utils/feedback/types';
import {useListItemCheckboxContext} from 'sentry/utils/list/useListItemCheckboxState';
import {decodeScalar} from 'sentry/utils/queryString';
import {useReplayCountForFeedbacks} from 'sentry/utils/replayCount/useReplayCountForFeedbacks';
import {useLocationQuery} from 'sentry/utils/url/useLocationQuery';
Expand All @@ -26,9 +27,7 @@ import {makeFeedbackPathname} from 'sentry/views/feedback/pathnames';

interface Props {
feedbackItem: FeedbackIssueListItem;
isSelected: 'all-selected' | boolean;
onItemSelect: () => void;
onSelect: (isSelected: boolean) => void;
}

function useIsSelectedFeedback({feedbackItem}: {feedbackItem: FeedbackIssueListItem}) {
Expand All @@ -39,12 +38,9 @@ function useIsSelectedFeedback({feedbackItem}: {feedbackItem: FeedbackIssueListI
return feedbackId === feedbackItem.id;
}

export function FeedbackListItem({
feedbackItem,
isSelected,
onSelect,
onItemSelect,
}: Props) {
export function FeedbackListItem({feedbackItem, onItemSelect}: Props) {
const {isSelected, toggleSelected} = useListItemCheckboxContext();

const organization = useOrganization();
const isOpen = useIsSelectedFeedback({feedbackItem});
const {feedbackHasReplay} = useReplayCountForFeedbacks();
Expand Down Expand Up @@ -84,10 +80,10 @@ export function FeedbackListItem({
}}
>
<Checkbox
disabled={isSelected === 'all-selected'}
checked={isSelected !== false}
onChange={e => {
onSelect(e.target.checked);
disabled={isSelected(feedbackItem.id) === 'all-selected'}
checked={isSelected(feedbackItem.id) !== false}
onChange={() => {
toggleSelected(feedbackItem.id);
}}
/>
</Row>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrar
import {ReplayBulkViewedActions} from 'sentry/components/replays/table/replayBulkViewedActions';
import {ProjectsStore} from 'sentry/stores/projectsStore';
import {trackAnalytics} from 'sentry/utils/analytics';
import type {ApiQueryKey} from 'sentry/utils/api/apiQueryKey';
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
import type {ListCheckboxQueryKeyRef} from 'sentry/utils/list/useListItemCheckboxState';
import type {ReplayListRecord} from 'sentry/views/explore/replays/types';

Expand Down Expand Up @@ -51,30 +49,19 @@ describe('ReplayBulkViewedActions', () => {
const replay = createReplay();

const deselectAll = jest.fn();
const apiUrl = getApiUrl(
'/projects/$organizationIdOrSlug/$projectIdOrSlug/replays/$replayId/viewed-by/',
{
path: {
organizationIdOrSlug: organization.id,
projectIdOrSlug: 2,
replayId: replay.id,
},
}
);
const queryKey: ApiQueryKey = [apiUrl, {}, {infinite: false}];
const queryKeyRef = {current: queryKey};
const endpointOptionsRef: ListCheckboxQueryKeyRef = {current: {query: {}}};

const renderWithOrganization = (
overrides: {
queryKeyRef?: ListCheckboxQueryKeyRef;
endpointOptionsRef?: ListCheckboxQueryKeyRef;
replays?: ReplayListRecord[];
selectedIds?: string[];
} = {}
) =>
render(
<ReplayBulkViewedActions
deselectAll={deselectAll}
queryKeyRef={overrides.queryKeyRef ?? queryKeyRef}
endpointOptionsRef={overrides.endpointOptionsRef ?? endpointOptionsRef}
replays={overrides.replays ?? [replay]}
selectedIds={overrides.selectedIds ?? [replay.id]}
/>,
Expand Down
23 changes: 12 additions & 11 deletions static/app/components/replays/table/replayBulkViewedActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,15 @@ import {LoadingIndicator} from 'sentry/components/loadingIndicator';
import {IconCheckmark} from 'sentry/icons';
import {t, tn} from 'sentry/locale';
import {trackAnalytics} from 'sentry/utils/analytics';
import type {ApiResponse} from 'sentry/utils/api/apiFetch';
import type {ListCheckboxQueryKeyRef} from 'sentry/utils/list/useListItemCheckboxState';
import {fetchMutation} from 'sentry/utils/queryClient';
import {replayListApiOptions} from 'sentry/utils/replays/replayListApiOptions';
import {useOrganization} from 'sentry/utils/useOrganization';
import type {
HydratedReplayRecord,
ReplayListRecord,
} from 'sentry/views/explore/replays/types';
import type {ReplayListRecord} from 'sentry/views/explore/replays/types';

interface Props {
deselectAll: () => void;
queryKeyRef: ListCheckboxQueryKeyRef;
endpointOptionsRef: ListCheckboxQueryKeyRef;
replays: ReplayListRecord[];
selectedIds: string[];
}
Expand All @@ -28,7 +25,7 @@ export function ReplayBulkViewedActions({
deselectAll,
replays,
selectedIds,
queryKeyRef,
endpointOptionsRef,
}: Props) {
const organization = useOrganization();
const queryClient = useQueryClient();
Expand All @@ -55,10 +52,14 @@ export function ReplayBulkViewedActions({
);

if (succeededIds.size) {
if (queryKeyRef.current) {
// eslint-disable-next-line @sentry/no-query-data-type-parameters
queryClient.setQueryData<ApiResponse<{data: HydratedReplayRecord[]}>>(
queryKeyRef.current,
if (endpointOptionsRef.current) {
const replayListOptions = replayListApiOptions({
options: endpointOptionsRef.current,
organization,
queryReferrer: 'replayList',
});
queryClient.setQueryData(
replayListOptions.queryKey,
Comment thread
cursor[bot] marked this conversation as resolved.
old =>
old && {
...old,
Expand Down
13 changes: 5 additions & 8 deletions static/app/components/replays/table/replayTableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
} from 'sentry/components/replays/table/replayTableColumns';
import {SimpleTable} from 'sentry/components/tables/simpleTable';
import {t, tct, tn} from 'sentry/locale';
import {parseQueryKey} from 'sentry/utils/api/apiQueryKey';
import type {Sort} from 'sentry/utils/discover/fields';
import {useListItemCheckboxContext} from 'sentry/utils/list/useListItemCheckboxState';
import type {ReplayListRecord} from 'sentry/views/explore/replays/types';
Expand All @@ -38,14 +37,12 @@ export function ReplayTableHeader({
deselectAll,
isAllSelected,
isAnySelected,
queryKeyRef,
endpointOptionsRef,
selectAll,
selectedIds,
} = listItemCheckboxState;
const queryOptions = queryKeyRef.current
? parseQueryKey(queryKeyRef.current).options
: undefined;
const queryString = queryOptions?.query?.query as string | undefined;
const endpointOptions = endpointOptionsRef.current;
const queryString = endpointOptions?.query?.query as string | undefined;

const headerStyle: React.CSSProperties = stickyHeader
? {position: 'sticky', top: 0}
Expand Down Expand Up @@ -89,13 +86,13 @@ export function ReplayTableHeader({
{selectedIds !== 'all' && (
<ReplayBulkViewedActions
deselectAll={deselectAll}
queryKeyRef={queryKeyRef}
endpointOptionsRef={endpointOptionsRef}
replays={replays}
selectedIds={selectedIds}
/>
)}
<DeleteReplays
queryOptions={queryOptions}
queryOptions={endpointOptionsRef.current}
replays={replays}
selectedIds={selectedIds}
/>
Expand Down
Loading
Loading