Skip to content

Commit

Permalink
translate and refactor extract html/selector
Browse files Browse the repository at this point in the history
  • Loading branch information
c298lee committed Aug 29, 2024
1 parent 65fe5b2 commit 199c9eb
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 66 deletions.
46 changes: 24 additions & 22 deletions static/app/components/replays/breadcrumbs/breadcrumbItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,16 @@ function BreadcrumbItem({
}, [expandPaths, frame, onInspectorExpanded, onMouseEnter, onMouseLeave, replay]);

const renderCodeSnippet = useCallback(() => {
return (!isSpanFrame(frame) || (isSpanFrame(frame) && !isWebVitalFrame(frame))) &&
extraction?.html
? extraction?.html.map(html => (
<CodeContainer key={html}>
<CodeSnippet language="html" hideCopyButton>
{beautify.html(html, {indent_size: 2})}
</CodeSnippet>
</CodeContainer>
))
: null;
return (
(!isSpanFrame(frame) || (isSpanFrame(frame) && !isWebVitalFrame(frame))) &&
extraction?.html?.map(html => (
<CodeContainer key={html}>
<CodeSnippet language="html" hideCopyButton>
{beautify.html(html, {indent_size: 2})}
</CodeSnippet>
</CodeContainer>
))
);
}, [extraction?.html, frame]);

const renderIssueLink = useCallback(() => {
Expand Down Expand Up @@ -243,19 +243,19 @@ function WebVitalData({
};

const {data: frameToExtraction} = useExtractDomNodes({replay});
const selectors = frameToExtraction?.get(frame)?.selector;
const selectors = frameToExtraction?.get(frame)?.selectors;

const webVitalData = {value: frame.data.value};
if (
frame.description === 'cumulative-layout-shift' &&
// frame.data.attributes &&
clsFrame.description === 'cumulative-layout-shift' &&
clsFrame.data.attributes &&
selectors
) {
const layoutShifts: {[x: string]: ReactNode[]}[] = [];
for (const attr of clsFrame.data.attributes) {
const elements: ReactNode[] = [];
attr.nodeIds?.map(nodeId => {
return selectors.get(nodeId)
attr.nodeIds?.forEach(nodeId => {
selectors.get(nodeId)
? elements.push(
<span
key={nodeId}
Expand All @@ -275,9 +275,9 @@ function WebVitalData({
if (!elements.length) {
elements.push(
<span>
<ValueObjectKey>{'element'}</ValueObjectKey>
<ValueObjectKey>{t('element')}</ValueObjectKey>
<span>{': '}</span>
<ValueNull>{'unknown'}</ValueNull>
<ValueNull>{t('unknown')}</ValueNull>
</span>
);
}
Expand All @@ -290,16 +290,18 @@ function WebVitalData({
const vitalKey = 'element';
webVitalData[vitalKey] = (
<span>
{Array.from(selectors).map(([, key]) => {
{Array.from(selectors).map(([value, key]) => {
return (
<span
key={key}
onMouseEnter={() => onMouseEnter(frame)}
onMouseLeave={() => onMouseLeave(frame)}
onMouseEnter={() => onMouseEnter(frame, value)}
onMouseLeave={() => onMouseLeave(frame, value)}
>
<ValueObjectKey>{'element'}</ValueObjectKey>
<ValueObjectKey>{t('element')}</ValueObjectKey>
<span>{': '}</span>
<SelectorButton>{key}</SelectorButton>
<Button size="zero" borderless>
{key}
</Button>
</span>
);
})}
Expand Down
86 changes: 46 additions & 40 deletions static/app/utils/replays/extractHtml.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,61 +6,67 @@ import constructSelector from 'sentry/views/replays/deadRageClick/constructSelec
export type Extraction = {
frame: ReplayFrame;
html: string[];
selector: Map<number, string>;
selectors: Map<number, string>;
timestamp: number;
};

export default function extractHtml(nodeIds: number[], mirror: Mirror): string[] {
export default function extractHtmlAndSelector(
nodeIds: number[],
mirror: Mirror
): {html: string[]; selectors: Map<number, string>} {
const htmlStrings: string[] = [];
const selectors = new Map<number, string>();
for (const nodeId of nodeIds) {
const node = mirror.getNode(nodeId);
if (node) {
const html = extractHtml(node);
if (html) {
htmlStrings.push(html);
}

const html =
(node && 'outerHTML' in node ? (node.outerHTML as string) : node?.textContent) ||
'';
// Limit document node depth to 2
let truncated = removeNodesAtLevel(html, 2);
// If still very long and/or removeNodesAtLevel failed, truncate
if (truncated.length > 1500) {
truncated = truncated.substring(0, 1500);
}
if (truncated) {
htmlStrings.push(truncated);
const selector = extractSelector(node);
if (selector) {
selectors.set(nodeId, selector);
}
}
}
return htmlStrings;
return {html: htmlStrings, selectors};
}

export function extractSelectorFromNodeIds(
nodeIds: number[],
mirror: Mirror
): Map<number, string> {
const selectors = new Map<number, string>();
for (const nodeId of nodeIds) {
const node = mirror.getNode(nodeId);
export function extractHtml(node: Node): string | null {
const html =
('outerHTML' in node ? (node.outerHTML as string) : node.textContent) || '';
// Limit document node depth to 2
let truncated = removeNodesAtLevel(html, 2);
// If still very long and/or removeNodesAtLevel failed, truncate
if (truncated.length > 1500) {
truncated = truncated.substring(0, 1500);
}
if (truncated) {
return truncated;
}
return null;
}

const element = node?.nodeType === Node.ELEMENT_NODE ? (node as HTMLElement) : null;
export function extractSelector(node: Node): string | null {
const element = node.nodeType === Node.ELEMENT_NODE ? (node as HTMLElement) : null;

if (element) {
selectors.set(
nodeId,
constructSelector({
alt: element.attributes.getNamedItem('alt')?.nodeValue ?? '',
aria_label: element.attributes.getNamedItem('aria-label')?.nodeValue ?? '',
class: element.attributes.getNamedItem('class')?.nodeValue?.split(' ') ?? [],
component_name:
element.attributes.getNamedItem('data-sentry-component')?.nodeValue ?? '',
id: element.id,
role: element.attributes.getNamedItem('role')?.nodeValue ?? '',
tag: element.tagName.toLowerCase(),
testid: element.attributes.getNamedItem('data-test-id')?.nodeValue ?? '',
title: element.attributes.getNamedItem('title')?.nodeValue ?? '',
}).selector
);
}
if (element) {
return constructSelector({
alt: element.attributes.getNamedItem('alt')?.nodeValue ?? '',
aria_label: element.attributes.getNamedItem('aria-label')?.nodeValue ?? '',
class: element.attributes.getNamedItem('class')?.nodeValue?.split(' ') ?? [],
component_name:
element.attributes.getNamedItem('data-sentry-component')?.nodeValue ?? '',
id: element.id,
role: element.attributes.getNamedItem('role')?.nodeValue ?? '',
tag: element.tagName.toLowerCase(),
testid: element.attributes.getNamedItem('data-test-id')?.nodeValue ?? '',
title: element.attributes.getNamedItem('title')?.nodeValue ?? '',
}).selector;
}

return selectors;
return null;
}

function removeChildLevel(max: number, collection: HTMLCollection, current: number = 0) {
Expand Down
7 changes: 3 additions & 4 deletions static/app/utils/replays/replayReader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {defined} from 'sentry/utils';
import domId from 'sentry/utils/domId';
import localStorageWrapper from 'sentry/utils/localStorage';
import clamp from 'sentry/utils/number/clamp';
import extractHtml, {extractSelectorFromNodeIds} from 'sentry/utils/replays/extractHtml';
import extractHtmlandSelector from 'sentry/utils/replays/extractHtml';
import hydrateBreadcrumbs, {
replayInitBreadcrumb,
} from 'sentry/utils/replays/hydrateBreadcrumbs';
Expand Down Expand Up @@ -151,12 +151,11 @@ const extractDomNodes = {
onVisitFrame: (frame, collection, replayer) => {
const mirror = replayer.getMirror();
const nodeIds = getNodeIds(frame);
const html = extractHtml(nodeIds as number[], mirror);
const selector = extractSelectorFromNodeIds(nodeIds as number[], mirror);
const {html, selectors} = extractHtmlandSelector((nodeIds ?? []) as number[], mirror);
collection.set(frame as ReplayFrame, {
frame,
html,
selector,
selectors,
timestamp: frame.timestampMs,
});
},
Expand Down

0 comments on commit 199c9eb

Please sign in to comment.