import type {ReactNode} from 'react'; import {useCallback, useMemo} from 'react'; import styled from '@emotion/styled'; import type {Location} from 'history'; import {PlatformIcon} from 'platformicons'; import {CodeSnippet} from 'sentry/components/codeSnippet'; import type {GridColumnOrder} from 'sentry/components/gridEditable'; import GridEditable from 'sentry/components/gridEditable'; import Link from 'sentry/components/links/link'; import renderSortableHeaderCell from 'sentry/components/replays/renderSortableHeaderCell'; import useQueryBasedColumnResize from 'sentry/components/replays/useQueryBasedColumnResize'; import useQueryBasedSorting from 'sentry/components/replays/useQueryBasedSorting'; import TextOverflow from 'sentry/components/textOverflow'; import {Tooltip} from 'sentry/components/tooltip'; import {IconCursorArrow} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import useProjects from 'sentry/utils/useProjects'; import {normalizeUrl} from 'sentry/utils/withDomainRequired'; import type {DeadRageSelectorItem} from 'sentry/views/replays/types'; import {WiderHovercard} from 'sentry/views/starfish/components/tableCells/spanDescriptionCell'; export interface UrlState { widths: string[]; } export function transformSelectorQuery(selector: string) { return selector .replaceAll('"', `\\"`) .replaceAll('aria=', 'aria-label=') .replaceAll('testid=', 'data-test-id=') .replaceAll(':', '\\:') .replaceAll('*', '\\*'); } interface Props { clickCountColumns: {key: string; name: string}[]; clickCountSortable: boolean; data: DeadRageSelectorItem[]; isError: boolean; isLoading: boolean; location: Location; title?: ReactNode; } const BASE_COLUMNS: GridColumnOrder[] = [ {key: 'project_id', name: 'project'}, {key: 'element', name: 'element'}, {key: 'dom_element', name: 'selector'}, {key: 'aria_label', name: 'aria label'}, ]; export function ProjectInfo({id, isWidget}: {id: number; isWidget: boolean}) { const {projects} = useProjects(); const project = projects.find(p => p.id === id.toString()); const platform = project?.platform; const slug = project?.slug; return isWidget ? ( ) : ( {slug} ); } export default function SelectorTable({ clickCountColumns, data, isError, isLoading, location, title, clickCountSortable, }: Props) { const {currentSort, makeSortLinkGenerator} = useQueryBasedSorting({ defaultSort: {field: clickCountColumns[0].key, kind: 'desc'}, location, }); const {columns, handleResizeColumn} = useQueryBasedColumnResize({ columns: BASE_COLUMNS.concat(clickCountColumns), location, }); const renderHeadCell = useMemo( () => renderSortableHeaderCell({ currentSort, makeSortLinkGenerator, onClick: () => {}, rightAlignedColumns: [], sortableColumns: clickCountSortable ? clickCountColumns : [], }), [currentSort, makeSortLinkGenerator, clickCountColumns, clickCountSortable] ); const queryPrefix = currentSort.field.includes('count_dead_clicks') ? 'dead' : 'rage'; const renderBodyCell = useCallback( (column, dataRow) => { const value = dataRow[column.key]; switch (column.key) { case 'dom_element': return ( ); case 'element': case 'aria_label': return {value}; case 'project_id': return ; default: return renderClickCount(column, dataRow); } }, [queryPrefix] ); const selectorEmptyMessage = ( {t('No dead or rage clicks found')} {t( 'There were no dead or rage clicks within this timeframe. Expand your timeframe, or increase your replay sample rate to see more data.' )} ); return ( } title={title} /> ); } export function SelectorLink({ value, selectorQuery, projectId, }: { projectId: string; selectorQuery: string; value: string; }) { const organization = useOrganization(); const location = useLocation(); const hovercardContent = ( {t('Search for replays with clicks on the element')} {value} ); return ( {value} ); } function renderClickCount(column: GridColumnOrder, dataRow: T) { const color = column.key === 'count_dead_clicks' ? 'yellow300' : 'red300'; return ( {dataRow[column.key]} ); } const ClickCount = styled(TextOverflow)` color: ${p => p.theme.gray400}; display: grid; grid-template-columns: auto auto; gap: ${space(0.75)}; align-items: center; justify-content: start; `; const StyledTextOverflow = styled(TextOverflow)` color: ${p => p.theme.blue300}; `; const TooltipContainer = styled('div')` display: grid; grid-auto-flow: row; gap: ${space(1)}; `; const SelectorScroll = styled('div')` overflow: scroll; `; const Subtitle = styled('div')` font-size: ${p => p.theme.fontSizeMedium}; `; const Title = styled('div')` font-size: 24px; `; const MessageContainer = styled('div')` display: grid; grid-auto-flow: row; gap: ${space(1)}; justify-items: center; text-align: center; padding: ${space(4)}; `; const WidgetProjectContainer = styled('div')` display: flex; flex-direction: row; align-items: center; gap: ${space(0.75)}; `; const IndexProjectContainer = styled(WidgetProjectContainer)` padding-right: ${space(1)}; `;