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

feat(replays): add initial replay cards to replay list #53323

Merged
merged 10 commits into from
Jul 21, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import useReplayList from 'sentry/utils/replays/hooks/useReplayList';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import useReplaysFromIssue from 'sentry/views/issueDetails/groupReplays/useReplaysFromIssue';
import ReplayTable from 'sentry/views/replays/replayTable';
import {ReplayTable} from 'sentry/views/replays/replayTable';
import {ReplayColumn} from 'sentry/views/replays/replayTable/types';
import type {ReplayListLocationQuery} from 'sentry/views/replays/types';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import PageLayout, {
ChildProps,
} from 'sentry/views/performance/transactionSummary/pageLayout';
import Tab from 'sentry/views/performance/transactionSummary/tabs';
import ReplayTable from 'sentry/views/replays/replayTable';
import {ReplayTable} from 'sentry/views/replays/replayTable';
import {ReplayColumn} from 'sentry/views/replays/replayTable/types';
import type {ReplayListLocationQuery} from 'sentry/views/replays/types';

Expand Down
2 changes: 2 additions & 0 deletions static/app/views/replays/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {t} from 'sentry/locale';
import useReplayPageview from 'sentry/utils/replays/hooks/useReplayPageview';
import useOrganization from 'sentry/utils/useOrganization';
import ReplaysFilters from 'sentry/views/replays/list/filters';
import ReplaysErroneousDeadRageCards from 'sentry/views/replays/list/replaysErroneousDeadRageCards';
import ReplaysList from 'sentry/views/replays/list/replaysList';

function ReplaysListContainer() {
Expand All @@ -30,6 +31,7 @@ function ReplaysListContainer() {
<PageFiltersContainer>
<Layout.Body>
<Layout.Main fullWidth>
<ReplaysErroneousDeadRageCards />
<ReplaysFilters />
<ReplaysList />
</Layout.Main>
Expand Down
139 changes: 139 additions & 0 deletions static/app/views/replays/list/replaysErroneousDeadRageCards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import {Fragment} from 'react';
import styled from '@emotion/styled';
import {Location} from 'history';

import {space} from 'sentry/styles/space';
import type {Organization} from 'sentry/types';
import EventView from 'sentry/utils/discover/eventView';
import useReplayList from 'sentry/utils/replays/hooks/useReplayList';
import {useHaveSelectedProjectsSentAnyReplayEvents} from 'sentry/utils/replays/hooks/useReplayOnboarding';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import {CardReplayTable} from 'sentry/views/replays/replayTable';
import {ReplayColumn} from 'sentry/views/replays/replayTable/types';

function ReplaysErroneousDeadRageCards() {
const location = useLocation();
const organization = useOrganization();

const eventViewErrors = EventView.fromNewQueryWithLocation(
michellewzhang marked this conversation as resolved.
Show resolved Hide resolved
{
name: '',
version: 2,
fields: [
'activity',
'duration',
'count_errors',
'id',
'project_id',
'user',
'finished_at',
'is_archived',
'started_at',
],
range: '1d',
projects: [],
query: '',
orderby: '-count_errors',
},
location
michellewzhang marked this conversation as resolved.
Show resolved Hide resolved
);

const eventViewDeadRage = EventView.fromNewQueryWithLocation(
{
name: '',
version: 2,
fields: [
'activity',
'duration',
'count_dead_clicks',
'count_rage_clicks',
'id',
'project_id',
'user',
'finished_at',
'is_archived',
'started_at',
],
range: '2d',
projects: [],
query: '',
orderby: '-count_dead_clicks',
},
location
);

const hasSessionReplay = organization.features.includes('session-replay');
const hasDeadRageCards = organization.features.includes('replay-error-click-cards');
const {hasSentOneReplay, fetching} = useHaveSelectedProjectsSentAnyReplayEvents();

const deadRageCols = [
ReplayColumn.MOST_DEAD_CLICKS,
ReplayColumn.COUNT_DEAD_CLICKS,
ReplayColumn.COUNT_RAGE_CLICKS,
];

const errorCols = [
ReplayColumn.MOST_ERRONEOUS_REPLAYS,
ReplayColumn.DURATION,
ReplayColumn.COUNT_ERRORS,
ReplayColumn.ACTIVITY,
];

return hasSessionReplay && !fetching && hasSentOneReplay ? (
hasDeadRageCards ? (
<SplitCardContainer>
<CardTable
eventView={eventViewErrors}
location={location}
organization={organization}
visibleColumns={errorCols}
/>
<CardTable
eventView={eventViewDeadRage}
location={location}
organization={organization}
visibleColumns={deadRageCols}
/>
</SplitCardContainer>
) : null
) : null;
}

function CardTable({
eventView,
location,
organization,
visibleColumns,
}: {
eventView: EventView;
location: Location;
organization: Organization;
visibleColumns: ReplayColumn[];
}) {
const {replays, isFetching, fetchError} = useReplayList({
eventView,
location,
organization,
});

return (
<Fragment>
<CardReplayTable
fetchError={fetchError}
isFetching={isFetching}
replays={replays?.slice(0, 3)}
sort={eventView.sorts[0]}
Copy link
Member

Choose a reason for hiding this comment

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

what if this was sort={undefined}

Copy link
Member Author

Choose a reason for hiding this comment

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

YES

visibleColumns={visibleColumns}
/>
</Fragment>
);
}

const SplitCardContainer = styled('div')`
display: grid;
grid-template-columns: 1fr 1fr;
gap: ${space(2)};
`;
Comment on lines +138 to +142
Copy link
Member

Choose a reason for hiding this comment

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

Grid! looks so easy once it's working!


export default ReplaysErroneousDeadRageCards;
4 changes: 2 additions & 2 deletions static/app/views/replays/list/replaysList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import useOrganization from 'sentry/utils/useOrganization';
import usePageFilters from 'sentry/utils/usePageFilters';
import useProjectSdkNeedsUpdate from 'sentry/utils/useProjectSdkNeedsUpdate';
import ReplayOnboardingPanel from 'sentry/views/replays/list/replayOnboardingPanel';
import ReplayTable from 'sentry/views/replays/replayTable';
import {ReplayTable} from 'sentry/views/replays/replayTable';
import {ReplayColumn} from 'sentry/views/replays/replayTable/types';
import type {ReplayListLocationQuery} from 'sentry/views/replays/types';
import {getReplayListFields} from 'sentry/views/replays/types';
Expand Down Expand Up @@ -101,9 +101,9 @@ function ReplaysListTable({
ReplayColumn.OS,
ReplayColumn.BROWSER,
ReplayColumn.DURATION,
ReplayColumn.COUNT_ERRORS,
ReplayColumn.COUNT_DEAD_CLICKS,
ReplayColumn.COUNT_RAGE_CLICKS,
ReplayColumn.COUNT_ERRORS,
ReplayColumn.ACTIVITY,
]
: [
Expand Down
35 changes: 27 additions & 8 deletions static/app/views/replays/replayTable/headerCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import {ReplayColumn} from 'sentry/views/replays/replayTable/types';

type Props = {
column: ReplayColumn;
headersSortable?: boolean;
sort?: Sort;
};

function HeaderCell({column, sort}: Props) {
function HeaderCell({column, sort, headersSortable}: Props) {
switch (column) {
case ReplayColumn.ACTIVITY:
return (
<SortableHeader
sort={sort}
sort={headersSortable ? sort : undefined}
fieldName="activity"
label={t('Activity')}
tooltip={t(
Expand All @@ -28,39 +29,57 @@ function HeaderCell({column, sort}: Props) {
case ReplayColumn.COUNT_DEAD_CLICKS:
return (
<SortableHeader
sort={sort}
sort={headersSortable ? sort : undefined}
fieldName="count_dead_clicks"
label={t('Dead Clicks')}
label={t('Dead clicks')}
tooltip={t(
'A dead click is a user click that does not result in any page activity after 7 seconds.'
)}
/>
);

case ReplayColumn.COUNT_ERRORS:
return <SortableHeader sort={sort} fieldName="count_errors" label={t('Errors')} />;
return (
<SortableHeader
sort={headersSortable ? sort : undefined}
fieldName="count_errors"
label={t('Errors')}
/>
);

case ReplayColumn.COUNT_RAGE_CLICKS:
return (
<SortableHeader
sort={sort}
sort={headersSortable ? sort : undefined}
fieldName="count_rage_clicks"
label={t('Rage Clicks')}
label={t('Rage clicks')}
tooltip={t(
'A rage click is 5 or more clicks on a dead element, which exhibits no page activity after 7 seconds.'
)}
/>
);

case ReplayColumn.DURATION:
return <SortableHeader sort={sort} fieldName="duration" label={t('Duration')} />;
return (
<SortableHeader
sort={headersSortable ? sort : undefined}
fieldName="duration"
label={t('Duration')}
/>
);

case ReplayColumn.OS:
return <SortableHeader sort={sort} fieldName="os.name" label={t('OS')} />;

case ReplayColumn.REPLAY:
return <SortableHeader sort={sort} fieldName="started_at" label={t('Replay')} />;

case ReplayColumn.MOST_ERRONEOUS_REPLAYS:
return <SortableHeader label={t('Most erroneous replays')} />;

case ReplayColumn.MOST_DEAD_CLICKS:
return <SortableHeader label={t('Most dead clicks')} />;

case ReplayColumn.SLOWEST_TRANSACTION:
return (
<SortableHeader
Expand Down
Loading