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 } +export function SmallReadOnlyDisplay(): JSX.Element { + return +} + export function WithoutCentralHighlightDisplay(): JSX.Element { return } @@ -212,3 +216,15 @@ export function EditableDisplayWithPreselection(): JSX.Element { export function WithUniquenessCheck(): JSX.Element { return } + +export function SmallWithUniquenessCheck(): JSX.Element { + return ( + + ) +} diff --git a/frontend/src/lib/components/HTMLElementsDisplay/HTMLElementsDisplay.tsx b/frontend/src/lib/components/HTMLElementsDisplay/HTMLElementsDisplay.tsx index 4e2c0b4c8809b..c00bca5b711d1 100644 --- a/frontend/src/lib/components/HTMLElementsDisplay/HTMLElementsDisplay.tsx +++ b/frontend/src/lib/components/HTMLElementsDisplay/HTMLElementsDisplay.tsx @@ -1,3 +1,4 @@ +import clsx from 'clsx' import { useActions, useValues } from 'kea' import { htmlElementsDisplayLogic } from 'lib/components/HTMLElementsDisplay/htmlElementsDisplayLogic' import { ParsedCSSSelector } from 'lib/components/HTMLElementsDisplay/preselectWithCSS' @@ -13,7 +14,13 @@ function indent(level: number): string { return Array(level).fill(' ').join('') } -function CloseAllTags({ elements }: { elements: ElementType[] }): JSX.Element { +function CloseAllTags({ + elements, + size = 'small', +}: { + elements: ElementType[] + size?: 'small' | 'xsmall' +}): JSX.Element { return ( <> {[...elements] @@ -28,7 +35,10 @@ function CloseAllTags({ elements }: { elements: ElementType[] }): JSX.Element { }} >
                             {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 && (
Selector:
-
{chosenSelector}
+
{chosenSelector}
)} @@ -161,7 +176,10 @@ export function HTMLElementsDisplay({ <> {elementsToShowDepth ? (
@@ -177,8 +195,9 @@ export function HTMLElementsDisplay({
                             parsedCSSSelectors={parsedSelectors}
                             onChange={(index, s) => setParsedSelectors({ ...parsedSelectors, [index]: s })}
                             selectedText={selectedText}
+                            size={size}
                         />
-                        
+                        
                     
                 ) : (
                     
No elements to display
diff --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 */} +
+ + Autocapture image src +
+ ) + } + + 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 ( + }> + Autocapture image src + + ) + } + + 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 */} - Autocapture image src -
- } - > - Autocapture image src -
- ) - } - - 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' ? ( ) : 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)}
+                                        
+ ), + }, + ]} + /> ) ) : (