From cfc26984efad2ef336ec72bc5c4d2084727e5237 Mon Sep 17 00:00:00 2001 From: Adrian de la Rosa Date: Thu, 12 Mar 2026 12:48:19 +0100 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20Add=20view=5Fupdate=20support?= =?UTF-8?q?=20to=20developer=20extension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - eventRow.tsx: add view_update to event type color map - eventRow.tsx: add ViewUpdateDescription component showing document version, view name, and count of changed fields - eventRow.tsx: fix URL construction crash for relative URLs in getViewName - eventCollection.ts: handle nullable date in compareEvents (view_update has optional date per ViewProperties schema) --- .../components/tabs/eventsTab/eventRow.tsx | 42 ++++++++++++++++++- .../panel/hooks/useEvents/eventCollection.ts | 2 +- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx b/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx index 4444cab422..8e0a64406e 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx @@ -11,6 +11,7 @@ import type { RumLongTaskEvent, RumResourceEvent, RumViewEvent, + RumViewUpdateEvent, RumVitalEvent, } from '../../../../../../packages/rum-core/src/rumEvent.types' import type { SdkEvent } from '../../../sdkEvent' @@ -31,11 +32,11 @@ const RUM_EVENT_TYPE_COLOR = { error: 'red', long_task: 'yellow', view: 'blue', + view_update: 'blue', resource: 'cyan', telemetry: 'teal', vital: 'orange', transition: 'green', - view_update: 'blue', } const LOG_STATUS_COLOR = { @@ -288,6 +289,8 @@ export const EventDescription = React.memo(({ event }: { event: SdkEvent }) => { switch (event.type) { case 'view': return + case 'view_update': + return case 'long_task': return case 'error': @@ -338,6 +341,34 @@ function ViewDescription({ event }: { event: RumViewEvent }) { ) } +// view.id is the only field always present in a view_update diff (routing field) +const VIEW_UPDATE_REQUIRED_KEYS = new Set(['id']) + +function ViewUpdateDescription({ event }: { event: RumViewUpdateEvent }) { + const changedFieldCount = event.view + ? Object.keys(event.view).filter((k) => !VIEW_UPDATE_REQUIRED_KEYS.has(k)).length + : 0 + const viewName = event.view ? event.view.name || event.view.url : undefined + + return ( + <> + View update v{event._dd.document_version} + {viewName && ( + <> + {' '} + · {viewName} + + )} + {changedFieldCount > 0 && ( + <> + {' '} + · {changedFieldCount} {changedFieldCount === 1 ? 'field' : 'fields'} changed + + )} + + ) +} + function ActionDescription({ event }: { event: RumActionEvent }) { const actionName = event.action.target?.name const frustrationTypes = event.action.frustration?.type @@ -425,5 +456,12 @@ function Emphasis({ children }: { children: ReactNode }) { } function getViewName(view: { name?: string; url: string }) { - return `${view.name || new URL(view.url).pathname}` + if (view.name) { + return view.name + } + try { + return new URL(view.url).pathname + } catch { + return view.url + } } diff --git a/developer-extension/src/panel/hooks/useEvents/eventCollection.ts b/developer-extension/src/panel/hooks/useEvents/eventCollection.ts index cabe19636f..c8be0211e0 100644 --- a/developer-extension/src/panel/hooks/useEvents/eventCollection.ts +++ b/developer-extension/src/panel/hooks/useEvents/eventCollection.ts @@ -37,7 +37,7 @@ export function startEventCollection(strategy: EventCollectionStrategy, onEvents function compareEvents(a: SdkEvent, b: SdkEvent) { // Sort events chronologically if (a.date !== b.date) { - return b.date - a.date + return (b.date ?? 0) - (a.date ?? 0) } // If two events have the same date, make sure to display View events last. This ensures that View From e23b09e9bb2d9dae5f2da715c7531d20943255c5 Mon Sep 17 00:00:00 2001 From: Adrian de la Rosa Date: Fri, 13 Mar 2026 14:57:14 +0100 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=91=8C=20Address=20code=20review:=20f?= =?UTF-8?q?ix=20=5Fdd=20optional=20crash,=20viewName=20URL=20normalization?= =?UTF-8?q?,=20comment=20and=20naming=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../panel/components/tabs/eventsTab/eventRow.tsx | 14 +++++++++----- .../src/panel/hooks/useEvents/eventCollection.ts | 12 ++++++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx b/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx index 8e0a64406e..ef51c6d7cb 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx @@ -341,18 +341,22 @@ function ViewDescription({ event }: { event: RumViewEvent }) { ) } -// view.id is the only field always present in a view_update diff (routing field) -const VIEW_UPDATE_REQUIRED_KEYS = new Set(['id']) +// view.id is always present in a view_update diff as a routing field to identify the view +const VIEW_UPDATE_ROUTING_KEYS = new Set(['id']) function ViewUpdateDescription({ event }: { event: RumViewUpdateEvent }) { const changedFieldCount = event.view - ? Object.keys(event.view).filter((k) => !VIEW_UPDATE_REQUIRED_KEYS.has(k)).length + ? Object.keys(event.view).filter((k) => !VIEW_UPDATE_ROUTING_KEYS.has(k)).length : 0 - const viewName = event.view ? event.view.name || event.view.url : undefined + const viewName = event.view + ? event.view.url + ? getViewName({ name: event.view.name, url: event.view.url }) + : event.view.name + : undefined return ( <> - View update v{event._dd.document_version} + View update{event._dd && v{event._dd.document_version}} {viewName && ( <> {' '} diff --git a/developer-extension/src/panel/hooks/useEvents/eventCollection.ts b/developer-extension/src/panel/hooks/useEvents/eventCollection.ts index c8be0211e0..85539db167 100644 --- a/developer-extension/src/panel/hooks/useEvents/eventCollection.ts +++ b/developer-extension/src/panel/hooks/useEvents/eventCollection.ts @@ -40,14 +40,14 @@ function compareEvents(a: SdkEvent, b: SdkEvent) { return (b.date ?? 0) - (a.date ?? 0) } - // If two events have the same date, make sure to display View events last. This ensures that View - // updates are collocated in the list (no other event are present between two updates) + // If two events have the same date, make sure to display type:view events last. This ensures that + // view events are collocated in the list (no other events appear between two view updates). // - // For example, we can receive an initial View event, then a 'document' Resource event, then a - // View event update. All of those events have the same date (navigationStart). If we only relied + // For example, we can receive an initial type:view event, then a 'document' Resource event, then + // a type:view update. All of those events have the same date (navigationStart). If we only relied // on the event date, events would be displayed in the order they are received, so the Resource - // event would be displayed between the two View events, which makes it a bit confusing. This - // ensures that all View updates are displayed before the Resource event. + // event would be displayed between the two type:view events, which is confusing. This ensures + // that type:view events are grouped at the end. return (isRumViewEvent(a) as any) - (isRumViewEvent(b) as any) } From 60763a17ef50dd899bdb2c325371913bbafd0ff6 Mon Sep 17 00:00:00 2001 From: Adrian de la Rosa Date: Tue, 31 Mar 2026 14:11:56 +0200 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=94=A7=20Fix=20sort=20regression=20an?= =?UTF-8?q?d=20remove=20unnecessary=20type=20cast=20in=20developer=20exten?= =?UTF-8?q?sion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/panel/components/tabs/eventsTab/eventRow.tsx | 9 +-------- .../src/panel/hooks/useEvents/eventCollection.ts | 4 ++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx b/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx index ef51c6d7cb..4192a64110 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx @@ -460,12 +460,5 @@ function Emphasis({ children }: { children: ReactNode }) { } function getViewName(view: { name?: string; url: string }) { - if (view.name) { - return view.name - } - try { - return new URL(view.url).pathname - } catch { - return view.url - } + return view.name ?? new URL(view.url).pathname } diff --git a/developer-extension/src/panel/hooks/useEvents/eventCollection.ts b/developer-extension/src/panel/hooks/useEvents/eventCollection.ts index 85539db167..60df82c1b0 100644 --- a/developer-extension/src/panel/hooks/useEvents/eventCollection.ts +++ b/developer-extension/src/panel/hooks/useEvents/eventCollection.ts @@ -37,7 +37,7 @@ export function startEventCollection(strategy: EventCollectionStrategy, onEvents function compareEvents(a: SdkEvent, b: SdkEvent) { // Sort events chronologically if (a.date !== b.date) { - return (b.date ?? 0) - (a.date ?? 0) + return b.date - a.date } // If two events have the same date, make sure to display type:view events last. This ensures that @@ -48,7 +48,7 @@ function compareEvents(a: SdkEvent, b: SdkEvent) { // on the event date, events would be displayed in the order they are received, so the Resource // event would be displayed between the two type:view events, which is confusing. This ensures // that type:view events are grouped at the end. - return (isRumViewEvent(a) as any) - (isRumViewEvent(b) as any) + return Number(isRumViewEvent(a)) - Number(isRumViewEvent(b)) } function listenEventsFromRequests(callback: (events: SdkEvent[]) => void) {