Skip to content

Commit de249dc

Browse files
ryan953andrewshie-sentry
authored andcommitted
feat(replay): Add checkboxes to the replay list page to enable bulk operations (#95191)
This adds checkboxes to the replay list page which allow bulk selecting of some or all replays. You can select all _visible_ replays (so like, 1 page of data) or _all_ replays that match your search terms (query, daterange, project, env) Right now there are no bulk actions implemented, I added the placeholder "TODO: Add a delete action here." for now. This is guarded behind the flag `feature.organizations:replay-list-select`. | State | Img | | --- | --- | | Nothing Selected | <img width="1039" alt="unselected" src="https://github.com/user-attachments/assets/e78c1ceb-1b5c-443e-8b45-f697b9f045bb" /> | Some selected | <img width="1024" height="286" alt="SCR-20250710-slsj" src="https://github.com/user-attachments/assets/13c91a34-2ce3-4376-851e-917ffc60868b" /> | Small result set, all selected | <img width="1030" alt="small results - all selected" src="https://github.com/user-attachments/assets/8f6462e3-e760-41da-82ab-f5503f2b41da" /> | Large result set, visible page selected | <img width="1030" alt="large results - one page selected" src="https://github.com/user-attachments/assets/45d4620e-d93b-446a-887e-9190b981e6d8" /> | Large result set, all pages selected | <img width="1031" alt="large results - all selected" src="https://github.com/user-attachments/assets/db62afe5-8304-49a7-875a-997e14b3d567" /> Depends on #95188 Fixes REPLAY-509
1 parent e73358e commit de249dc

File tree

10 files changed

+490
-156
lines changed

10 files changed

+490
-156
lines changed

static/app/components/replays/table/replayTable.tsx

Lines changed: 21 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import type {HTMLAttributes, ReactNode} from 'react';
21
import styled from '@emotion/styled';
32

43
import {Alert} from 'sentry/components/core/alert';
54
import InteractionStateLayer from 'sentry/components/core/interactionStateLayer';
6-
import {Tooltip} from 'sentry/components/core/tooltip';
75
import LoadingIndicator from 'sentry/components/loadingIndicator';
86
import type {ReplayTableColumn} from 'sentry/components/replays/table/replayTableColumns';
9-
import {ReplaySessionColumn} from 'sentry/components/replays/table/replayTableColumns';
7+
import ReplayTableHeader from 'sentry/components/replays/table/replayTableHeader';
108
import {SimpleTable} from 'sentry/components/tables/simpleTable';
119
import {t} from 'sentry/locale';
1210
import type {Sort} from 'sentry/utils/discover/fields';
@@ -40,46 +38,42 @@ export default function ReplayTable({
4038
showDropdownFilters,
4139
sort,
4240
}: Props) {
41+
const gridTemplateColumns = columns.map(col => col.width ?? 'max-content').join(' ');
42+
4343
if (isPending) {
4444
return (
45-
<ReplayTableWithColumns
45+
<StyledSimpleTable
4646
data-test-id="replay-table-loading"
47-
columns={columns}
48-
sort={sort}
49-
onSortClick={onSortClick}
47+
style={{gridTemplateColumns}}
5048
>
49+
<ReplayTableHeader columns={columns} onSortClick={onSortClick} sort={sort} />
5150
<SimpleTable.Empty>
5251
<LoadingIndicator />
5352
</SimpleTable.Empty>
54-
</ReplayTableWithColumns>
53+
</StyledSimpleTable>
5554
);
5655
}
5756

5857
if (error) {
5958
return (
60-
<ReplayTableWithColumns
59+
<StyledSimpleTable
6160
data-test-id="replay-table-errored"
62-
columns={columns}
63-
sort={sort}
64-
onSortClick={onSortClick}
61+
style={{gridTemplateColumns}}
6562
>
63+
<ReplayTableHeader columns={columns} onSortClick={onSortClick} sort={sort} />
6664
<SimpleTable.Empty>
6765
<Alert type="error" showIcon>
6866
{t('Sorry, the list of replays could not be loaded. ')}
6967
{getErrorMessage(error)}
7068
</Alert>
7169
</SimpleTable.Empty>
72-
</ReplayTableWithColumns>
70+
</StyledSimpleTable>
7371
);
7472
}
7573

7674
return (
77-
<ReplayTableWithColumns
78-
data-test-id="replay-table"
79-
columns={columns}
80-
sort={sort}
81-
onSortClick={onSortClick}
82-
>
75+
<StyledSimpleTable data-test-id="replay-table" style={{gridTemplateColumns}}>
76+
<ReplayTableHeader columns={columns} onSortClick={onSortClick} sort={sort} />
8377
{replays.length === 0 && (
8478
<SimpleTable.Empty>{t('No replays found')}</SimpleTable.Empty>
8579
)}
@@ -110,57 +104,18 @@ export default function ReplayTable({
110104
</SimpleTable.Row>
111105
);
112106
})}
113-
</ReplayTableWithColumns>
107+
</StyledSimpleTable>
114108
);
115109
}
116110

117-
type TableProps = {
118-
children: ReactNode;
119-
columns: readonly ReplayTableColumn[];
120-
onSortClick?: (key: string) => void;
121-
sort?: Sort;
122-
} & HTMLAttributes<HTMLTableElement>;
123-
124-
const ReplayTableWithColumns = styled(
125-
({children, columns, onSortClick, sort, ...props}: TableProps) => (
126-
<SimpleTable {...props}>
127-
<SimpleTable.Header>
128-
{columns.map((column, columnIndex) => (
129-
<SimpleTable.HeaderCell
130-
key={`${column.name}-${columnIndex}`}
131-
handleSortClick={() => column.sortKey && onSortClick?.(column.sortKey)}
132-
sort={
133-
column.sortKey && sort?.field === column.sortKey ? sort.kind : undefined
134-
}
135-
>
136-
<Tooltip title={column.tooltip} disabled={!column.tooltip}>
137-
{column.name}
138-
</Tooltip>
139-
</SimpleTable.HeaderCell>
140-
))}
141-
</SimpleTable.Header>
142-
143-
{children}
144-
</SimpleTable>
145-
)
146-
)`
147-
${p => getGridTemplateColumns(p.columns)}
148-
margin-bottom: 0;
111+
const StyledSimpleTable = styled(SimpleTable)`
149112
overflow: auto;
150113
151114
[data-clickable='true'] {
152115
cursor: pointer;
153116
}
154117
`;
155118

156-
function getGridTemplateColumns(columns: readonly ReplayTableColumn[]) {
157-
return `grid-template-columns: ${columns
158-
.map(column =>
159-
column === ReplaySessionColumn ? 'minmax(150px, 1fr)' : 'max-content'
160-
)
161-
.join(' ')};`;
162-
}
163-
164119
function getErrorMessage(fetchError: RequestError) {
165120
if (typeof fetchError === 'string') {
166121
return fetchError;
@@ -193,7 +148,13 @@ const RowCell = styled(SimpleTable.RowCell)`
193148
position: relative;
194149
overflow: auto;
195150
151+
/* Used for cell menu items that are hidden by default */
196152
&:hover [data-visible-on-hover='true'] {
197153
opacity: 1;
198154
}
155+
156+
/* Used for the main replay display name in ReplaySessionColumn */
157+
&:hover [data-underline-on-hover='true'] {
158+
text-decoration: underline;
159+
}
199160
`;

0 commit comments

Comments
 (0)