import type {ReactNode} from 'react';
import {memo} from 'react';
import styled from '@emotion/styled';
import {Alert} from 'sentry/components/alert';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import {PanelTable} from 'sentry/components/panels/panelTable';
import {t} from 'sentry/locale';
import EventView from 'sentry/utils/discover/eventView';
import type {Sort} from 'sentry/utils/discover/fields';
import getRouteStringFromRoutes from 'sentry/utils/getRouteStringFromRoutes';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import {useRoutes} from 'sentry/utils/useRoutes';
import useUrlParams from 'sentry/utils/useUrlParams';
import type {ReplayListRecordWithTx} from 'sentry/views/performance/transactionSummary/transactionReplays/useReplaysWithTxData';
import HeaderCell from 'sentry/views/replays/replayTable/headerCell';
import {
ActivityCell,
BrowserCell,
DeadClickCountCell,
DurationCell,
ErrorCountCell,
OSCell,
PlayPauseCell,
RageClickCountCell,
ReplayCell,
TransactionCell,
} from 'sentry/views/replays/replayTable/tableCell';
import {ReplayColumn} from 'sentry/views/replays/replayTable/types';
import type {ReplayListRecord} from 'sentry/views/replays/types';
type Props = {
fetchError: null | undefined | Error;
isFetching: boolean;
replays: undefined | ReplayListRecord[] | ReplayListRecordWithTx[];
sort: Sort | undefined;
visibleColumns: ReplayColumn[];
emptyMessage?: ReactNode;
gridRows?: string;
onClickPlay?: (index: number) => void;
referrerLocation?: string;
showDropdownFilters?: boolean;
};
// Memoizing this component to avoid unnecessary re-renders
const ReplayTable = memo(
({
fetchError,
isFetching,
replays,
sort,
visibleColumns,
emptyMessage,
gridRows,
showDropdownFilters,
onClickPlay,
referrerLocation,
}: Props) => {
const routes = useRoutes();
const location = useLocation();
const organization = useOrganization();
// we may have a selected replay index in the URLs
const urlParams = useUrlParams();
const rawReplayIndex = urlParams.getParamValue('selected_replay_index');
const selectedReplayIndex = parseInt(
typeof rawReplayIndex === 'string' ? rawReplayIndex : '0',
10
);
const tableHeaders = visibleColumns
.filter(Boolean)
.map(column => );
if (fetchError && !isFetching) {
return (
{typeof fetchError === 'string'
? fetchError
: t(
'Sorry, the list of replays could not be loaded. This could be due to invalid search parameters or an internal systems error.'
)}
);
}
const referrer = getRouteStringFromRoutes(routes);
const eventView = EventView.fromLocation(location);
return (
}
disableHeaderBorderBottom
>
{replays?.map(
(replay: ReplayListRecord | ReplayListRecordWithTx, index: number) => {
return (
onClickPlay?.(index)}
showCursor={onClickPlay !== undefined}
referrerLocation={referrerLocation}
>
{visibleColumns.map(column => {
switch (column) {
case ReplayColumn.ACTIVITY:
return (
);
case ReplayColumn.BROWSER:
return (
);
case ReplayColumn.COUNT_DEAD_CLICKS:
return (
);
case ReplayColumn.COUNT_ERRORS:
return (
);
case ReplayColumn.COUNT_RAGE_CLICKS:
return (
);
case ReplayColumn.DURATION:
return (
);
case ReplayColumn.OS:
return (
);
case ReplayColumn.REPLAY:
return (
);
case ReplayColumn.PLAY_PAUSE:
return (
onClickPlay?.(index)}
/>
);
case ReplayColumn.SLOWEST_TRANSACTION:
return (
);
default:
return null;
}
})}
);
}
)}
);
}
);
const StyledPanelTable = styled(PanelTable)<{
visibleColumns: ReplayColumn[];
gridRows?: string;
}>`
margin-bottom: 0;
grid-template-columns: ${p =>
p.visibleColumns
.filter(Boolean)
.map(column => (column === 'replay' ? 'minmax(100px, 1fr)' : 'max-content'))
.join(' ')};
${props =>
props.gridRows
? `grid-template-rows: ${props.gridRows};`
: `grid-template-rows: 44px max-content;`}
`;
const StyledAlert = styled(Alert)`
border-radius: 0;
border-width: 1px 0 0 0;
grid-column: 1/-1;
margin-bottom: 0;
`;
const Row = styled('div')<{
isPlaying?: boolean;
referrerLocation?: string;
showCursor?: boolean;
}>`
${p =>
p.referrerLocation === 'replay'
? `display: contents;
& > * {
border-top: 1px solid ${p.theme.border};
}`
: `display: contents;
& > * {
background-color: ${p.isPlaying ? p.theme.translucentGray200 : 'inherit'};
border-top: 1px solid ${p.theme.border};
cursor: ${p.showCursor ? 'pointer' : 'default'};
}
:hover {
background-color: ${p.showCursor ? p.theme.translucentInnerBorder : 'inherit'};
}
:active {
background-color: ${p.theme.translucentGray200};
}
`}
`;
export default ReplayTable;