From 19b325eb53090f57174d0e1c64dd80a5fffe5c7f Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 3 Sep 2026 13:33:22 +0200 Subject: [PATCH] feat(names): Add span name and description rules for `ui.*` spans --- .../sentry-conventions/src/attributes.ts | 49 ++++++++ .../attributes/ui/ui__element__selector.json | 21 ++++ model/description/ui.json | 40 ++++++ model/name/ui.json | 117 ++++++++++++++++++ python/src/sentry_conventions/attributes.py | 33 +++++ 5 files changed, 260 insertions(+) create mode 100644 model/attributes/ui/ui__element__selector.json create mode 100644 model/description/ui.json create mode 100644 model/name/ui.json diff --git a/javascript/sentry-conventions/src/attributes.ts b/javascript/sentry-conventions/src/attributes.ts index 2fd0a583..21a2867c 100644 --- a/javascript/sentry-conventions/src/attributes.ts +++ b/javascript/sentry-conventions/src/attributes.ts @@ -17188,6 +17188,28 @@ export const UI_ELEMENT_RENDER_TIME = 'ui.element.render_time'; */ export type UI_ELEMENT_RENDER_TIME_TYPE = number; +// Path: model/attributes/ui/ui__element__selector.json + +/** + * The HTML element selector or component name of the UI element a span refers to, for example the element a user interacted with. `ui.element.selector` + * + * Attribute Value Type: `string` {@link UI_ELEMENT_SELECTOR_TYPE} + * + * Apply Scrubbing: manual + * + * Attribute defined in OTEL: No + * Visibility: public + * + * @example "body > div#app > div#container > button.submit" + * @example "HomeButton" + */ +export const UI_ELEMENT_SELECTOR = 'ui.element.selector'; + +/** + * Type for {@link UI_ELEMENT_SELECTOR} ui.element.selector + */ +export type UI_ELEMENT_SELECTOR_TYPE = string; + // Path: model/attributes/ui/ui__element__type.json /** @@ -19442,6 +19464,7 @@ export const ATTRIBUTE_TYPE: Record = { 'ui.element.load_time': 'double', 'ui.element.paint_type': 'string', 'ui.element.render_time': 'double', + 'ui.element.selector': 'string', 'ui.element.type': 'string', 'ui.element.url': 'string', 'ui.element.width': 'integer', @@ -20277,6 +20300,7 @@ export type AttributeName = | typeof UI_ELEMENT_LOAD_TIME | typeof UI_ELEMENT_PAINT_TYPE | typeof UI_ELEMENT_RENDER_TIME + | typeof UI_ELEMENT_SELECTOR | typeof UI_ELEMENT_TYPE | typeof UI_ELEMENT_URL | typeof UI_ELEMENT_WIDTH @@ -32584,6 +32608,23 @@ export const ATTRIBUTE_METADATA: Record = { example: 1023.1124, changelog: [{ version: '0.5.0', prs: [284], description: 'Added ui.element.render_time attribute' }], }, + 'ui.element.selector': { + brief: + 'The HTML element selector or component name of the UI element a span refers to, for example the element a user interacted with.', + type: 'string', + keys: ['ui.element.selector'], + applyScrubbing: { + key: 'manual', + }, + isInOtel: false, + visibility: 'public', + example: 'body > div#app > div#container > button.submit', + examples: ['body > div#app > div#container > button.submit', 'HomeButton'], + changelog: [{ version: 'next', prs: [620], description: 'Added ui.element.selector attribute' }], + additionalContext: [ + 'Typically a CSS-like path built from the DOM tree of the element. Web vital spans use the vital-specific `browser.web_vital.*` attributes (e.g. `browser.web_vital.lcp.element`) instead.', + ], + }, 'ui.element.type': { brief: 'type of the UI element', type: 'string', @@ -38301,6 +38342,13 @@ export const ATTRIBUTE_SEARCH_METADATA: Record brief: 'The rendering time of the UI element (from time origin to finished rendering)', deprecationChain: ['ui.element.render_time'], }, + 'ui.element.selector': { + canonicalName: 'ui.element.selector', + type: 'string', + brief: + 'The HTML element selector or component name of the UI element a span refers to, for example the element a user interacted with.', + deprecationChain: ['ui.element.selector'], + }, 'ui.element.type': { canonicalName: 'ui.element.type', type: 'string', @@ -39470,6 +39518,7 @@ export type Attributes = { [UI_ELEMENT_LOAD_TIME]?: UI_ELEMENT_LOAD_TIME_TYPE; [UI_ELEMENT_PAINT_TYPE]?: UI_ELEMENT_PAINT_TYPE_TYPE; [UI_ELEMENT_RENDER_TIME]?: UI_ELEMENT_RENDER_TIME_TYPE; + [UI_ELEMENT_SELECTOR]?: UI_ELEMENT_SELECTOR_TYPE; [UI_ELEMENT_TYPE]?: UI_ELEMENT_TYPE_TYPE; [UI_ELEMENT_URL]?: UI_ELEMENT_URL_TYPE; [UI_ELEMENT_WIDTH]?: UI_ELEMENT_WIDTH_TYPE; diff --git a/model/attributes/ui/ui__element__selector.json b/model/attributes/ui/ui__element__selector.json new file mode 100644 index 00000000..7ea4edc8 --- /dev/null +++ b/model/attributes/ui/ui__element__selector.json @@ -0,0 +1,21 @@ +{ + "key": "ui.element.selector", + "brief": "The HTML element selector or component name of the UI element a span refers to, for example the element a user interacted with.", + "type": "string", + "apply_scrubbing": { + "key": "manual" + }, + "is_in_otel": false, + "visibility": "public", + "examples": ["body > div#app > div#container > button.submit", "HomeButton"], + "additional_context": [ + "Typically a CSS-like path built from the DOM tree of the element. Web vital spans use the vital-specific `browser.web_vital.*` attributes (e.g. `browser.web_vital.lcp.element`) instead." + ], + "changelog": [ + { + "version": "next", + "prs": [620], + "description": "Added ui.element.selector attribute" + } + ] +} diff --git a/model/description/ui.json b/model/description/ui.json new file mode 100644 index 00000000..d7849742 --- /dev/null +++ b/model/description/ui.json @@ -0,0 +1,40 @@ +{ + "brief": "UI", + "operations": [ + { + "name": "Component lifecycle", + "brief": "Mounting, updating, rendering or unmounting of a UI component.", + "ops": ["ui.mount", "ui.update", "ui.render", "ui.unmount"], + "templates": ["{{ui.component_name}}"], + "examples": ["HomeButton"] + }, + { + "name": "Long animation frame", + "brief": "A long animation frame, as reported by the Long Animation Frames API.", + "ops": ["ui.long_animation_frame"], + "templates": ["{{browser.script.invoker}}", "{{code.function.name}}", "{{code.file.path}}"], + "examples": ["Window.requestAnimationFrame", "IMG#hero.onload", "onScroll", "https://example.com/static/app.js"] + }, + { + "name": "User action", + "brief": "A user action and everything it triggers.", + "ops": ["ui.action", "ui.action.click"], + "templates": ["{{router.navigation.route.id}}", "{{url.template}}", "{{url.path}}"], + "examples": ["UserProfile", "/users/:id", "/users/123"] + }, + { + "name": "Interaction", + "brief": "An interaction measured via Interaction to Next Paint (INP).", + "ops": ["ui.interaction.click", "ui.interaction.hover", "ui.interaction.drag", "ui.interaction.press"], + "templates": ["{{ui.element.selector}}", "{{ui.component_name}}"], + "examples": ["body > div#app > div#container > button.submit", "HomeButton"] + }, + { + "name": "Largest Contentful Paint", + "brief": "A Largest Contentful Paint (LCP) web vital measurement.", + "ops": ["ui.webvital.lcp"], + "templates": ["{{browser.web_vital.lcp.element}}", "{{browser.web_vital.lcp.url}}"], + "examples": ["body > div#app > div#container > img", "https://assets.example.com/hero.png"] + } + ] +} diff --git a/model/name/ui.json b/model/name/ui.json new file mode 100644 index 00000000..f8424a85 --- /dev/null +++ b/model/name/ui.json @@ -0,0 +1,117 @@ +{ + "brief": "UI", + "operations": [ + { + "name": "Component mount", + "brief": "Mounting of a UI component or application (e.g. initial render/bootstrap).", + "is_in_otel": false, + "ops": ["ui.mount"], + "templates": ["{{ui.component_name}}", "Component mount"], + "examples": ["HomeButton", "Component mount"] + }, + { + "name": "Component update", + "brief": "Updating of an already-mounted UI component.", + "is_in_otel": false, + "ops": ["ui.update"], + "templates": ["{{ui.component_name}}", "Component update"], + "examples": ["HomeButton", "Component update"] + }, + { + "name": "Component render", + "brief": "Rendering of a UI component.", + "is_in_otel": false, + "ops": ["ui.render"], + "templates": ["{{ui.component_name}}", "Component render"], + "examples": ["HomeButton", "Component render"] + }, + { + "name": "Component unmount", + "brief": "Unmounting/teardown of a UI component.", + "is_in_otel": false, + "ops": ["ui.unmount"], + "templates": ["{{ui.component_name}}", "Component unmount"], + "examples": ["HomeButton", "Component unmount"] + }, + { + "name": "Long task", + "brief": "A task on the main UI thread, as reported by the Long Tasks API.", + "is_in_otel": false, + "ops": ["ui.task", "ui.long_task"], + "templates": ["Main UI thread blocked"], + "examples": ["Main UI thread blocked"] + }, + { + "name": "Long animation frame", + "brief": "A long animation frame, as reported by the Long Animation Frames API.", + "is_in_otel": false, + "ops": ["ui.long_animation_frame"], + "templates": ["{{browser.script.invoker_type}}", "Main UI thread blocked"], + "examples": ["event-listener", "user-callback", "classic-script", "Main UI thread blocked"] + }, + { + "name": "User action", + "brief": "A user action and everything it triggers, named after the route the user was on.", + "is_in_otel": false, + "ops": ["ui.action"], + "templates": ["{{router.navigation.route.id}}", "{{url.template}}", "User action"], + "examples": ["UserProfile", "/users/:id", "User action"] + }, + { + "name": "Click action", + "brief": "A click and everything it triggers, named after the route the user was on.", + "is_in_otel": false, + "ops": ["ui.action.click"], + "templates": ["{{router.navigation.route.id}}", "{{url.template}}", "Click"], + "examples": ["UserProfile", "/users/:id", "Click"] + }, + { + "name": "Click interaction", + "brief": "A click interaction measured via Interaction to Next Paint (INP).", + "is_in_otel": false, + "ops": ["ui.interaction.click"], + "templates": ["{{ui.component_name}}", "Click"], + "examples": ["HomeButton", "Click"] + }, + { + "name": "Hover interaction", + "brief": "A hover interaction measured via Interaction to Next Paint (INP).", + "is_in_otel": false, + "ops": ["ui.interaction.hover"], + "templates": ["{{ui.component_name}}", "Hover"], + "examples": ["HomeButton", "Hover"] + }, + { + "name": "Drag interaction", + "brief": "A drag interaction measured via Interaction to Next Paint (INP).", + "is_in_otel": false, + "ops": ["ui.interaction.drag"], + "templates": ["{{ui.component_name}}", "Drag"], + "examples": ["HomeButton", "Drag"] + }, + { + "name": "Key press interaction", + "brief": "A key press interaction measured via Interaction to Next Paint (INP).", + "is_in_otel": false, + "ops": ["ui.interaction.press"], + "templates": ["{{ui.component_name}}", "Key press"], + "examples": ["SearchInput", "Key press"] + }, + { + "name": "Largest Contentful Paint", + "brief": "A Largest Contentful Paint (LCP) web vital measurement.", + "is_in_otel": false, + "ops": ["ui.webvital.lcp"], + "templates": ["Largest contentful paint"], + "examples": ["Largest contentful paint"] + }, + { + "name": "Cumulative Layout Shift", + "brief": "A Cumulative Layout Shift (CLS) web vital measurement.", + "is_in_otel": false, + "ops": ["ui.webvital.cls"], + "templates": ["Layout shift"], + "examples": ["Layout shift"] + } + ] +} diff --git a/python/src/sentry_conventions/attributes.py b/python/src/sentry_conventions/attributes.py index 755ec49d..7e2a38bd 100644 --- a/python/src/sentry_conventions/attributes.py +++ b/python/src/sentry_conventions/attributes.py @@ -10042,6 +10042,18 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): Example: 1023.1124 """ + # Path: model/attributes/ui/ui__element__selector.json + UI_ELEMENT_SELECTOR: Literal["ui.element.selector"] = "ui.element.selector" + """The HTML element selector or component name of the UI element a span refers to, for example the element a user interacted with. + + Type: str + Apply Scrubbing: manual + Defined in OTEL: No + Visibility: public + Example: "body > div#app > div#container > button.submit" + Example: "HomeButton" + """ + # Path: model/attributes/ui/ui__element__type.json UI_ELEMENT_TYPE: Literal["ui.element.type"] = "ui.element.type" """type of the UI element @@ -25165,6 +25177,26 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): ), ], ), + "ui.element.selector": AttributeMetadata( + brief="The HTML element selector or component name of the UI element a span refers to, for example the element a user interacted with.", + type=AttributeType.STRING, + keys=("ui.element.selector",), + apply_scrubbing=ApplyScrubbingInfo(key=ApplyScrubbing.MANUAL), + is_in_otel=False, + visibility=Visibility.PUBLIC, + example="body > div#app > div#container > button.submit", + examples=["body > div#app > div#container > button.submit", "HomeButton"], + changelog=[ + ChangelogEntry( + version="next", + prs=[620], + description="Added ui.element.selector attribute", + ), + ], + additional_context=[ + "Typically a CSS-like path built from the DOM tree of the element. Web vital spans use the vital-specific `browser.web_vital.*` attributes (e.g. `browser.web_vital.lcp.element`) instead." + ], + ), "ui.element.type": AttributeMetadata( brief="type of the UI element", type=AttributeType.STRING, @@ -26850,6 +26882,7 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): "ui.element.load_time": float, "ui.element.paint_type": str, "ui.element.render_time": float, + "ui.element.selector": str, "ui.element.type": str, "ui.element.url": str, "ui.element.width": int,