diff --git a/frontend/__snapshots__/components-html-elements-display--editable-display--dark.png b/frontend/__snapshots__/components-html-elements-display--editable-display--dark.png
index 1db0b9cfa879c..9026ab8970dca 100644
Binary files a/frontend/__snapshots__/components-html-elements-display--editable-display--dark.png and b/frontend/__snapshots__/components-html-elements-display--editable-display--dark.png differ
diff --git a/frontend/__snapshots__/components-html-elements-display--editable-display--light.png b/frontend/__snapshots__/components-html-elements-display--editable-display--light.png
index 4173cbeaa918e..1613b8a582275 100644
Binary files a/frontend/__snapshots__/components-html-elements-display--editable-display--light.png and b/frontend/__snapshots__/components-html-elements-display--editable-display--light.png differ
diff --git a/frontend/__snapshots__/components-html-elements-display--editable-display-with-preselection--dark.png b/frontend/__snapshots__/components-html-elements-display--editable-display-with-preselection--dark.png
index 76e461183a861..f810f72a08c38 100644
Binary files a/frontend/__snapshots__/components-html-elements-display--editable-display-with-preselection--dark.png and b/frontend/__snapshots__/components-html-elements-display--editable-display-with-preselection--dark.png differ
diff --git a/frontend/__snapshots__/components-html-elements-display--editable-display-with-preselection--light.png b/frontend/__snapshots__/components-html-elements-display--editable-display-with-preselection--light.png
index 40ba5f5bfc0ce..e915d0875abc1 100644
Binary files a/frontend/__snapshots__/components-html-elements-display--editable-display-with-preselection--light.png and b/frontend/__snapshots__/components-html-elements-display--editable-display-with-preselection--light.png differ
diff --git a/frontend/__snapshots__/components-html-elements-display--small-read-only-display--dark.png b/frontend/__snapshots__/components-html-elements-display--small-read-only-display--dark.png
new file mode 100644
index 0000000000000..6ee37359aa53d
Binary files /dev/null and b/frontend/__snapshots__/components-html-elements-display--small-read-only-display--dark.png differ
diff --git a/frontend/__snapshots__/components-html-elements-display--small-read-only-display--light.png b/frontend/__snapshots__/components-html-elements-display--small-read-only-display--light.png
new file mode 100644
index 0000000000000..741e349ac329a
Binary files /dev/null and b/frontend/__snapshots__/components-html-elements-display--small-read-only-display--light.png differ
diff --git a/frontend/__snapshots__/components-html-elements-display--small-with-uniqueness-check--dark.png b/frontend/__snapshots__/components-html-elements-display--small-with-uniqueness-check--dark.png
new file mode 100644
index 0000000000000..21c8c0c700b98
Binary files /dev/null and b/frontend/__snapshots__/components-html-elements-display--small-with-uniqueness-check--dark.png differ
diff --git a/frontend/__snapshots__/components-html-elements-display--small-with-uniqueness-check--light.png b/frontend/__snapshots__/components-html-elements-display--small-with-uniqueness-check--light.png
new file mode 100644
index 0000000000000..447fa7b473966
Binary files /dev/null and b/frontend/__snapshots__/components-html-elements-display--small-with-uniqueness-check--light.png differ
diff --git a/frontend/__snapshots__/components-html-elements-display--with-uniqueness-check--dark.png b/frontend/__snapshots__/components-html-elements-display--with-uniqueness-check--dark.png
index 0e7cb8823ec3c..21c8c0c700b98 100644
Binary files a/frontend/__snapshots__/components-html-elements-display--with-uniqueness-check--dark.png and b/frontend/__snapshots__/components-html-elements-display--with-uniqueness-check--dark.png differ
diff --git a/frontend/__snapshots__/components-html-elements-display--with-uniqueness-check--light.png b/frontend/__snapshots__/components-html-elements-display--with-uniqueness-check--light.png
index a7dbcb4a0693a..447fa7b473966 100644
Binary files a/frontend/__snapshots__/components-html-elements-display--with-uniqueness-check--light.png and b/frontend/__snapshots__/components-html-elements-display--with-uniqueness-check--light.png differ
diff --git a/frontend/__snapshots__/components-itemperformanceevent--default--dark.png b/frontend/__snapshots__/components-itemperformanceevent--default--dark.png
index 738adb9ae4874..0d632d9aebd7c 100644
Binary files a/frontend/__snapshots__/components-itemperformanceevent--default--dark.png and b/frontend/__snapshots__/components-itemperformanceevent--default--dark.png differ
diff --git a/frontend/__snapshots__/components-itemperformanceevent--default--light.png b/frontend/__snapshots__/components-itemperformanceevent--default--light.png
index def2f72b7e957..ff64856c25b41 100644
Binary files a/frontend/__snapshots__/components-itemperformanceevent--default--light.png and b/frontend/__snapshots__/components-itemperformanceevent--default--light.png differ
diff --git a/frontend/__snapshots__/components-itemperformanceevent--no-performance-observer-captured-data--dark.png b/frontend/__snapshots__/components-itemperformanceevent--no-performance-observer-captured-data--dark.png
index e42315417d6a5..8223b1cdc388e 100644
Binary files a/frontend/__snapshots__/components-itemperformanceevent--no-performance-observer-captured-data--dark.png and b/frontend/__snapshots__/components-itemperformanceevent--no-performance-observer-captured-data--dark.png differ
diff --git a/frontend/__snapshots__/components-itemperformanceevent--no-performance-observer-captured-data--light.png b/frontend/__snapshots__/components-itemperformanceevent--no-performance-observer-captured-data--light.png
index 712bc799ea5b6..a75dbc19890fc 100644
Binary files a/frontend/__snapshots__/components-itemperformanceevent--no-performance-observer-captured-data--light.png and b/frontend/__snapshots__/components-itemperformanceevent--no-performance-observer-captured-data--light.png differ
diff --git a/frontend/__snapshots__/components-playerinspector-itemevent--default--dark.png b/frontend/__snapshots__/components-playerinspector-itemevent--default--dark.png
index da00c576592b2..f6b293378bdfa 100644
Binary files a/frontend/__snapshots__/components-playerinspector-itemevent--default--dark.png and b/frontend/__snapshots__/components-playerinspector-itemevent--default--dark.png differ
diff --git a/frontend/__snapshots__/components-playerinspector-itemevent--default--light.png b/frontend/__snapshots__/components-playerinspector-itemevent--default--light.png
index 5f40cbab56c12..4befb45504e7c 100644
Binary files a/frontend/__snapshots__/components-playerinspector-itemevent--default--light.png and b/frontend/__snapshots__/components-playerinspector-itemevent--default--light.png differ
diff --git a/frontend/__snapshots__/components-playerinspector-itemevent--group-identify-event--dark.png b/frontend/__snapshots__/components-playerinspector-itemevent--group-identify-event--dark.png
index 3b8d51e0a588b..8b51f9557e6cf 100644
Binary files a/frontend/__snapshots__/components-playerinspector-itemevent--group-identify-event--dark.png and b/frontend/__snapshots__/components-playerinspector-itemevent--group-identify-event--dark.png differ
diff --git a/frontend/__snapshots__/components-playerinspector-itemevent--group-identify-event--light.png b/frontend/__snapshots__/components-playerinspector-itemevent--group-identify-event--light.png
index ff7b2fed50cf8..ade03b226963c 100644
Binary files a/frontend/__snapshots__/components-playerinspector-itemevent--group-identify-event--light.png and b/frontend/__snapshots__/components-playerinspector-itemevent--group-identify-event--light.png differ
diff --git a/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-current-url--dark.png b/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-current-url--dark.png
index 31e8fe4fe4b99..d029935f201bc 100644
Binary files a/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-current-url--dark.png and b/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-current-url--dark.png differ
diff --git a/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-current-url--light.png b/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-current-url--light.png
index d1eece3a24ae4..820708ec8b75b 100644
Binary files a/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-current-url--light.png and b/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-current-url--light.png differ
diff --git a/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-path--dark.png b/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-path--dark.png
index 7f12ee70567fe..3a560282282e4 100644
Binary files a/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-path--dark.png and b/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-path--dark.png differ
diff --git a/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-path--light.png b/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-path--light.png
index c872b968a5880..1766d60a0cc06 100644
Binary files a/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-path--light.png and b/frontend/__snapshots__/components-playerinspector-itemevent--page-view-with-path--light.png differ
diff --git a/frontend/__snapshots__/components-playerinspector-itemevent--web-vitals-event--dark.png b/frontend/__snapshots__/components-playerinspector-itemevent--web-vitals-event--dark.png
index 564cb99e745f6..b1dd2533af2ad 100644
Binary files a/frontend/__snapshots__/components-playerinspector-itemevent--web-vitals-event--dark.png and b/frontend/__snapshots__/components-playerinspector-itemevent--web-vitals-event--dark.png differ
diff --git a/frontend/__snapshots__/components-playerinspector-itemevent--web-vitals-event--light.png b/frontend/__snapshots__/components-playerinspector-itemevent--web-vitals-event--light.png
index cff106a904abb..1bb28c6fdabe3 100644
Binary files a/frontend/__snapshots__/components-playerinspector-itemevent--web-vitals-event--light.png and b/frontend/__snapshots__/components-playerinspector-itemevent--web-vitals-event--light.png differ
diff --git a/frontend/src/lib/components/HTMLElementsDisplay/HTMLElementsDisplay.stories.tsx b/frontend/src/lib/components/HTMLElementsDisplay/HTMLElementsDisplay.stories.tsx
index bb26f35d20a31..7cbfdd5a0a28c 100644
--- a/frontend/src/lib/components/HTMLElementsDisplay/HTMLElementsDisplay.stories.tsx
+++ b/frontend/src/lib/components/HTMLElementsDisplay/HTMLElementsDisplay.stories.tsx
@@ -181,6 +181,10 @@ export function ReadOnlyDisplay(): JSX.Element {
return
{indent(elements.length - index - 2)} @@ -47,6 +57,7 @@ function Tags({ editable, onChange, selectedText, + size = 'small', }: { elements: ElementType[] parsedCSSSelectors: Record@@ -54,6 +65,7 @@ function Tags({ editable: boolean onChange: (i: number, s: ParsedCSSSelector) => void selectedText?: string + size?: 'small' | 'xsmall' }): JSX.Element { return ( <> @@ -78,6 +90,7 @@ function Tags({ highlight={highlight} parsedCSSSelector={parsedCSSSelectors[index]} selectedText={selectedText} + size={size} /> ) @@ -92,6 +105,7 @@ interface HTMLElementsDisplayPropsBase { elements: ElementType[] highlight?: boolean selectedText?: string + size?: 'small' | 'xsmall' } type HTMLElementsDisplayProps = @@ -119,6 +133,7 @@ export function HTMLElementsDisplay({ highlight = true, editable = false, checkUniqueness = false, + size = 'small', }: HTMLElementsDisplayProps): JSX.Element { const [key] = useState(() => `HtmlElementsDisplay.${uniqueNode++}`) @@ -137,12 +152,12 @@ export function HTMLElementsDisplay({ const { setParsedSelectors, showAdditionalElements } = useActions(logic) return ( - +{editable && !!parsedElements.length && (- } - > - - - ) - } - - return null -} - export function ItemEvent({ item }: ItemEventProps): JSX.Element { const subValue = item.data.event === '$pageview' ? ( @@ -110,7 +64,7 @@ export function ItemEvent({ item }: ItemEventProps): JSX.Element { ) : item.data.event === '$web_vitals' ? ()} @@ -161,7 +176,10 @@ export function HTMLElementsDisplay({ <> {elementsToShowDepth ? (Selector:-{chosenSelector}+{chosenSelector}@@ -177,8 +195,9 @@ export function HTMLElementsDisplay({ parsedCSSSelectors={parsedSelectors} onChange={(index, s) => setParsedSelectors({ ...parsedSelectors, [index]: s })} selectedText={selectedText} + size={size} /> -+ > ) : ( No elements to displaydiff --git a/frontend/src/lib/components/HTMLElementsDisplay/SelectableElement.tsx b/frontend/src/lib/components/HTMLElementsDisplay/SelectableElement.tsx index 53a0fcb00d839..d5ce773e17b23 100644 --- a/frontend/src/lib/components/HTMLElementsDisplay/SelectableElement.tsx +++ b/frontend/src/lib/components/HTMLElementsDisplay/SelectableElement.tsx @@ -193,6 +193,7 @@ export function SelectableElement({ highlight, parsedCSSSelector, selectedText, + size = 'small', }: { element: ElementType isDeepestChild: boolean @@ -202,6 +203,7 @@ export function SelectableElement({ highlight?: boolean parsedCSSSelector?: ParsedCSSSelector selectedText?: string + size?: 'small' | 'xsmall' }): JSX.Element { const setParsedCSSSelector = (newParsedCSSSelector: ParsedCSSSelector): void => { if (!objectsEqual(newParsedCSSSelector, parsedCSSSelector)) { @@ -212,8 +214,9 @@ export function SelectableElement({ return ({indent} diff --git a/frontend/src/lib/taxonomy.tsx b/frontend/src/lib/taxonomy.tsx index ff5184cff43cf..79b1284b72a1f 100644 --- a/frontend/src/lib/taxonomy.tsx +++ b/frontend/src/lib/taxonomy.tsx @@ -279,16 +279,13 @@ export const CORE_FILTER_DEFINITIONS_BY_GROUP = { }, $lib_rate_limit_remaining_tokens: { label: 'Clientside rate limit remaining tokens', - description: ( - - Remaining rate limit tokens for the posthog-js library client-side rate limiting implementation. - - ), + description: + 'Remaining rate limit tokens for the posthog-js library client-side rate limiting implementation.', examples: ['100'], }, token: { label: 'Token', - description: Token used for authentication., + description: 'Token used for authentication.', examples: ['ph_abcdefg'], }, $ce_version: { @@ -335,12 +332,12 @@ export const CORE_FILTER_DEFINITIONS_BY_GROUP = { // session recording $replay_minimum_duration: { label: 'Replay config - minimum duration', - description: Config for minimum duration before emitting a session recording., + description: 'Config for minimum duration before emitting a session recording.', examples: ['1000'], }, $replay_sample_rate: { label: 'Replay config - sample rate', - description: Config for sampling rate of session recordings., + description: 'Config for sampling rate of session recordings.', examples: ['0.1'], }, $console_log_recording_enabled_server_side: { @@ -356,40 +353,39 @@ export const CORE_FILTER_DEFINITIONS_BY_GROUP = { }, $session_recording_start_reason: { label: 'Session recording start reason', - description: ( - - Reason for starting the session recording. Useful for e.g. if you have sampling enabled and want to - see on batch exported events which sessions have recordings available. - - ), + description: + 'Reason for starting the session recording. Useful for e.g. if you have sampling enabled and want to see on batch exported events which sessions have recordings available.', examples: ['sampling_override', 'recording_initialized', 'linked_flag_match'], }, $session_recording_canvas_recording: { label: 'Session recording canvas recording', - description: Session recording canvas capture config., + description: 'Session recording canvas capture config.', examples: ['{"enabled": false}'], }, $session_recording_network_payload_capture: { label: 'Session recording network payload capture', - description: Session recording network payload capture config., + description: 'Session recording network payload capture config.', examples: ['{"recordHeaders": false}'], }, + $configured_session_timeout_ms: { + label: 'Configured session timeout', + description: 'Configured session timeout in milliseconds.', + examples: ['1800000'], + }, + $replay_script_config: { + label: 'Replay script config', + description: 'Sets an alternative recorder script for the web sdk.', + examples: ['{"script": "recorder-next""}'], + }, $session_recording_url_trigger_activated_session: { label: 'Session recording URL trigger activated session', - description: ( - - Session recording URL trigger activated session config. Used by posthog-js to track URL activation - of session replay. - - ), + description: + 'Session recording URL trigger activated session config. Used by posthog-js to track URL activation of session replay.', }, $session_recording_url_trigger_status: { label: 'Session recording URL trigger status', - description: ( - - Session recording URL trigger status. Used by posthog-js to track URL activation of session replay. - - ), + description: + 'Session recording URL trigger status. Used by posthog-js to track URL activation of session replay.', }, $recording_status: { label: 'Session recording status', @@ -464,17 +460,17 @@ export const CORE_FILTER_DEFINITIONS_BY_GROUP = { }, $exception_capture_endpoint: { label: 'Exception capture endpoint', - description: Endpoint used by posthog-js exception autocapture., + description: 'Endpoint used by posthog-js exception autocapture.', examples: ['/e/'], }, $exception_capture_endpoint_suffix: { label: 'Exception capture endpoint', - description: Endpoint used by posthog-js exception autocapture., + description: 'Endpoint used by posthog-js exception autocapture.', examples: ['/e/'], }, $exception_capture_enabled_server_side: { label: 'Exception capture enabled server side', - description: Whether exception autocapture was enabled in remote config., + description: 'Whether exception autocapture was enabled in remote config.', }, // GeoIP @@ -1193,7 +1189,7 @@ export const CORE_FILTER_DEFINITIONS_BY_GROUP = { }, $web_vitals_allowed_metrics: { label: 'Web vitals allowed metrics', - description: Allowed web vitals metrics config., + description: 'Allowed web vitals metrics config.', examples: ['["LCP", "CLS"]'], }, @@ -1319,72 +1315,72 @@ export const CORE_FILTER_DEFINITIONS_BY_GROUP = { }, $start_timestamp: { label: 'Start timestamp', - description: The timestamp of the first event from this session., + description: 'The timestamp of the first event from this session.', examples: [new Date().toISOString()], }, $end_timestamp: { label: 'End timestamp', - description: The timestamp of the last event from this session, + description: 'The timestamp of the last event from this session', examples: [new Date().toISOString()], }, $entry_current_url: { label: 'Entry URL', - description: The first URL visited in this session, + description: 'The first URL visited in this session.', examples: ['https://example.com/interesting-article?parameter=true'], }, $entry_pathname: { label: 'Entry pathname', - description: The first pathname visited in this session, + description: 'The first pathname visited in this session.', examples: ['/interesting-article?parameter=true'], }, $end_current_url: { label: 'Entry URL', - description: The first URL visited in this session, + description: 'The first URL visited in this session.', examples: ['https://example.com/interesting-article?parameter=true'], }, $end_pathname: { label: 'Entry pathname', - description: The first pathname visited in this session, + description: 'The first pathname visited in this session.', examples: ['/interesting-article?parameter=true'], }, $exit_current_url: { label: 'Exit URL', - description: The last URL visited in this session, + description: 'The last URL visited in this session.', examples: ['https://example.com/interesting-article?parameter=true'], }, $exit_pathname: { label: 'Exit pathname', - description: The last pathname visited in this session, + description: 'The last pathname visited in this session.', examples: ['https://example.com/interesting-article?parameter=true'], }, $pageview_count: { label: 'Pageview count', - description: The number of page view events in this session, + description: 'The number of page view events in this session.', examples: ['123'], }, $autocapture_count: { label: 'Autocapture count', - description: The number of autocapture events in this session, + description: 'The number of autocapture events in this session.', examples: ['123'], }, $screen_count: { label: 'Screen count', - description: The number of screen events in this session, + description: 'The number of screen events in this session.', examples: ['123'], }, $channel_type: { label: 'Channel type', - description: What type of acquisition channel this traffic came from., + description: 'What type of acquisition channel this traffic came from.', examples: ['Paid Search', 'Organic Video', 'Direct'], }, $is_bounce: { label: 'Is bounce', - description: Whether the session was a bounce., + description: 'Whether the session was a bounce.', examples: ['true', 'false'], }, $last_external_click_url: { label: 'Last external click URL', - description: The last external URL clicked in this session, + description: 'The last external URL clicked in this session.', examples: ['https://example.com/interesting-article?parameter=true'], }, $vitals_lcp: { diff --git a/frontend/src/lib/utils/event-property-utls.tsx b/frontend/src/lib/utils/event-property-utls.tsx new file mode 100644 index 0000000000000..bd9ffbacb63ae --- /dev/null +++ b/frontend/src/lib/utils/event-property-utls.tsx @@ -0,0 +1,78 @@ +import { Tooltip } from 'lib/lemon-ui/Tooltip' + +import { ElementType } from '~/types' + +interface AutocapturedImage { + src: string | undefined + width: string | undefined + height: string | undefined +} + +export function autocaptureToImage(elements: ElementType[]): null | AutocapturedImage { + const find = elements.find((el) => el.tag_name === 'img') + const image = { + src: find?.attributes?.attr__src, + width: find?.attributes?.attr__width, + height: find?.attributes?.attr__height, + } + return image.src ? image : null +} + +export function AutocaptureImage({ img }: { img: AutocapturedImage }): JSX.Element | null { + if (img) { + return ( ++ {/* Transparent grid background */} + + + ++ ) + } + + return null +} + +export function AutocaptureImageTab({ elements }: { elements: ElementType[] }): JSX.Element | null { + const img = autocaptureToImage(elements) + if (img) { + return ( +++ ) + } + + return null +} + +export function AutocapturePreviewImage({ + elements, + imgPreviewHeight = '40', +}: { + elements: ElementType[] + imgPreviewHeight?: string +}): JSX.Element | null { + const img = autocaptureToImage(elements) + if (img) { + return ( ++ }> + + + ) + } + + return null +} diff --git a/frontend/src/scenes/activity/explore/EventDetails.tsx b/frontend/src/scenes/activity/explore/EventDetails.tsx index 1b7a1733cf48f..60f59f338f618 100644 --- a/frontend/src/scenes/activity/explore/EventDetails.tsx +++ b/frontend/src/scenes/activity/explore/EventDetails.tsx @@ -1,6 +1,5 @@ import './EventDetails.scss' -import { Properties } from '@posthog/plugin-scaffold' import { ErrorDisplay } from 'lib/components/Errors/ErrorDisplay' import { HTMLElementsDisplay } from 'lib/components/HTMLElementsDisplay/HTMLElementsDisplay' import { JSONViewer } from 'lib/components/JSONViewer' @@ -11,6 +10,7 @@ import { LemonTableProps } from 'lib/lemon-ui/LemonTable' import { LemonTabs } from 'lib/lemon-ui/LemonTabs' import { CORE_FILTER_DEFINITIONS_BY_GROUP, KNOWN_PROMOTED_PROPERTY_PARENTS } from 'lib/taxonomy' import { pluralize } from 'lib/utils' +import { AutocaptureImageTab, autocaptureToImage } from 'lib/utils/event-property-utls' import { useState } from 'react' import { EventType, PropertyDefinitionType } from '~/types' @@ -24,9 +24,9 @@ export function EventDetails({ event, tableProps }: EventDetailsProps): JSX.Elem const [showSystemProps, setShowSystemProps] = useState(false) const [activeTab, setActiveTab] = useState(event.event === '$exception' ? 'exception' : 'properties') - const displayedEventProperties: Properties = {} - const visibleSystemProperties: Properties = {} - const featureFlagProperties: Properties = {} + const displayedEventProperties = {} + const visibleSystemProperties = {} + const featureFlagProperties = {} let systemPropsCount = 0 for (const key of Object.keys(event.properties)) { if (CORE_FILTER_DEFINITIONS_BY_GROUP.events[key] && CORE_FILTER_DEFINITIONS_BY_GROUP.events[key].system) { @@ -111,6 +111,14 @@ export function EventDetails({ event, tableProps }: EventDetailsProps): JSX.Elem }) } + if (event.elements && autocaptureToImage(event.elements)) { + tabs.push({ + key: 'image', + label: 'Image', + content: , + }) + } + if (event.event === '$exception') { tabs.push({ key: 'exception', diff --git a/frontend/src/scenes/session-recordings/apm/playerInspector/ItemPerformanceEvent.tsx b/frontend/src/scenes/session-recordings/apm/playerInspector/ItemPerformanceEvent.tsx index 622d3fada3bb5..dc118953ea57d 100644 --- a/frontend/src/scenes/session-recordings/apm/playerInspector/ItemPerformanceEvent.tsx +++ b/frontend/src/scenes/session-recordings/apm/playerInspector/ItemPerformanceEvent.tsx @@ -66,8 +66,8 @@ export interface ItemPerformanceEventProps { finalTimestamp: Dayjs | null } -function renderTimeBenchmark(milliseconds: number): JSX.Element { - return ( +function renderTimeBenchmark(milliseconds: number | null): JSX.Element | null { + return milliseconds === null ? null : ( = 2000, @@ -107,14 +107,20 @@ function StartedAt({ item }: { item: PerformanceEvent }): JSX.Element | null { ) : null } -function DurationDescription({ item }: { item: PerformanceEvent }): JSX.Element | null { +function durationMillisecondsFrom(item: PerformanceEvent): number | null { let duration = item.duration if (duration === undefined && item.end_time !== undefined && item.start_time !== undefined) { duration = item.end_time - item.start_time } - if (duration === undefined) { + return duration ?? null +} + +function DurationDescription({ item }: { item: PerformanceEvent }): JSX.Element | null { + const duration = durationMillisecondsFrom(item) + if (duration === null) { return null } + return ( <> took {humanFriendlyMilliseconds(duration)} @@ -153,7 +159,7 @@ export function ItemPerformanceEvent({ item, finalTimestamp }: ItemPerformanceEv const sizeInfo = itemSizeInfo(item) const startTime = item.start_time || item.fetch_start || 0 - const duration = item.duration || item.end_time === undefined ? 0 : item.end_time - startTime + const duration = durationMillisecondsFrom(item) const callerOrigin = isURL(item.current_url) ? new URL(item.current_url).origin : undefined const eventName = item.name || '(empty string)' @@ -185,7 +191,7 @@ export function ItemPerformanceEvent({ item, finalTimestamp }: ItemPerformanceEv // eslint-disable-next-line react/forbid-dom-props style={{ left: `${(startTime / contextLengthMs) * 100}%`, - width: `${Math.max((duration / contextLengthMs) * 100, 0.5)}%`, + width: `${Math.max(((duration ?? 0) / contextLengthMs) * 100, 0.5)}%`, }} /> {item.entry_type === 'navigation' ? ( @@ -285,6 +291,7 @@ export function ItemPerformanceEventDetail({ item }: ItemPerformanceEventProps): setActiveTab(newKey)} tabs={[ diff --git a/frontend/src/scenes/session-recordings/player/inspector/components/ItemEvent.tsx b/frontend/src/scenes/session-recordings/player/inspector/components/ItemEvent.tsx index 6cd133ea5d92d..d5236311b6a47 100644 --- a/frontend/src/scenes/session-recordings/player/inspector/components/ItemEvent.tsx +++ b/frontend/src/scenes/session-recordings/player/inspector/components/ItemEvent.tsx @@ -1,23 +1,24 @@ import './ImagePreview.scss' -import { LemonButton, LemonDivider, Tooltip } from '@posthog/lemon-ui' +import { LemonButton, LemonDivider, LemonTabs } from '@posthog/lemon-ui' import { useValues } from 'kea' import { ErrorDisplay } from 'lib/components/Errors/ErrorDisplay' +import { HTMLElementsDisplay } from 'lib/components/HTMLElementsDisplay/HTMLElementsDisplay' import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo' import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types' import { TitledSnack } from 'lib/components/TitledSnack' import { IconOpenInNew } from 'lib/lemon-ui/icons' import { Spinner } from 'lib/lemon-ui/Spinner' -import { POSTHOG_EVENT_PROMOTED_PROPERTIES } from 'lib/taxonomy' +import { CORE_FILTER_DEFINITIONS_BY_GROUP, POSTHOG_EVENT_PROMOTED_PROPERTIES } from 'lib/taxonomy' import { autoCaptureEventToDescription, capitalizeFirstLetter, isString } from 'lib/utils' +import { AutocaptureImageTab, AutocapturePreviewImage, autocaptureToImage } from 'lib/utils/event-property-utls' +import { useState } from 'react' import { insightUrlForEvent } from 'scenes/insights/utils' import { eventPropertyFilteringLogic } from 'scenes/session-recordings/player/inspector/components/eventPropertyFilteringLogic' -import { DEFAULT_INSPECTOR_ROW_HEIGHT } from 'scenes/session-recordings/player/inspector/PlayerInspectorList' - -import { ElementType } from '~/types' import { InspectorListItemEvent } from '../playerInspectorLogic' import { SimpleKeyValueList } from './SimpleKeyValueList' + export interface ItemEventProps { item: InspectorListItemEvent } @@ -54,53 +55,6 @@ function SummarizeWebVitals({ properties }: { properties: Record }) ) } -function autocaptureToImage( - elements: ElementType[] -): null | { src: string | undefined; width: string | undefined; height: string | undefined } { - const find = elements.find((el) => el.tag_name === 'img') - const image = { - src: find?.attributes?.attr__src, - width: find?.attributes?.attr__width, - height: find?.attributes?.attr__height, - } - return image.src ? image : null -} - -function AutocaptureImage({ item }: ItemEventProps): JSX.Element | null { - const img = autocaptureToImage(item.data.elements) - if (img) { - return ( - - {/* Transparent grid background */} - - - {/* Image preview */} - - ) : item.data.elements.length ? ( - + ) : null return ( @@ -138,11 +92,26 @@ export function ItemEvent({ item }: ItemEventProps): JSX.Element { } export function ItemEventDetail({ item }: ItemEventProps): JSX.Element { + const [activeTab, setActiveTab] = useState<'properties' | 'flags' | 'image' | 'elements' | 'raw'>('properties') + const insightUrl = insightUrlForEvent(item.data) const { filterProperties } = useValues(eventPropertyFilteringLogic) const promotedKeys = POSTHOG_EVENT_PROMOTED_PROPERTIES[item.data.event] + const properties = {} + const featureFlagProperties = {} + + for (const key of Object.keys(item.data.properties)) { + if (!CORE_FILTER_DEFINITIONS_BY_GROUP.events[key] || !CORE_FILTER_DEFINITIONS_BY_GROUP.events[key].system) { + if (key.startsWith('$feature') || key === '$active_feature_flags') { + featureFlagProperties[key] = item.data.properties[key] + } else { + properties[key] = item.data.properties[key] + } + } + } + return ( @@ -168,7 +137,59 @@ export function ItemEventDetail({ item }: ItemEventProps): JSX.Element { item.data.event === '$exception' ? () : ( - + setActiveTab(newKey)} + tabs={[ + { + key: 'properties', + label: 'Properties', + content: ( + + ), + }, + { + key: 'flags', + label: 'Flags', + content: ( + + ), + }, + item.data.elements && item.data.elements.length > 0 + ? { + key: 'elements', + label: 'Elements', + content: ( + + ), + } + : null, + autocaptureToImage(item.data.elements) + ? { + key: 'image', + label: 'Image', + content: , + } + : null, + { + key: 'raw', + label: 'Raw', + content: ( + + {JSON.stringify(item.data.properties, null, 2)} ++ ), + }, + ]} + /> ) ) : (