diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index 2a4c05839c2..3162c7282dd 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -172,6 +172,15 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo uid === '*' || uid === metadata.name) !== -1, + onInteractionComplete: getDashboardInteractionCallback(metadata.name, dashboard.title), + }, + getDashboardSceneProfiler() + ); + const dashboardScene = new DashboardScene( { description: dashboard.description, @@ -200,6 +209,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo uid === '*' || uid === oldModel.uid) !== -1, + onInteractionComplete: getDashboardInteractionCallback(oldModel.uid, oldModel.title), + }, + getDashboardSceneProfiler() + ); + const behaviorList: SceneObjectState['$behaviors'] = [ new behaviors.CursorSync({ sync: oldModel.graphTooltip, }), queryController, + interactionTracker, registerDashboardMacro, registerPanelInteractionsReporter, new behaviors.LiveNowTimer({ enabled: oldModel.liveNow }), diff --git a/public/app/features/dashboard/services/DashboardProfiler.ts b/public/app/features/dashboard/services/DashboardProfiler.ts index af198ab2750..1be6cfd1f1c 100644 --- a/public/app/features/dashboard/services/DashboardProfiler.ts +++ b/public/app/features/dashboard/services/DashboardProfiler.ts @@ -10,6 +10,26 @@ export function getDashboardSceneProfiler() { return dashboardSceneProfiler; } +export function getDashboardComponentInteractionCallback(uid: string, title: string) { + return (e: SceneInteractionProfileEvent) => { + const payload = { + duration: e.duration, + networkDuration: e.networkDuration, + startTs: e.startTs, + endTs: e.endTs, + timeSinceBoot: performance.measure('time_since_boot', 'frontend_boot_js_done_time_seconds').duration, + }; + + reportInteraction('dashboard_interaction', { + interactionType: e.origin, + uid, + ...payload, + }); + + logMeasurement(`dashboard_interaction`, payload, { interactionType: e.origin, dashboard: uid, title: title }); + }; +} + export function getDashboardInteractionCallback(uid: string, title: string) { return (e: SceneInteractionProfileEvent) => { const payload = { diff --git a/public/app/features/scopes/selector/ScopesSelectorService.ts b/public/app/features/scopes/selector/ScopesSelectorService.ts index 5d8bf456f6a..92b00d16d96 100644 --- a/public/app/features/scopes/selector/ScopesSelectorService.ts +++ b/public/app/features/scopes/selector/ScopesSelectorService.ts @@ -1,4 +1,7 @@ import { Scope, ScopeNode, store as storeImpl } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { SceneRenderProfiler } from '@grafana/scenes'; +import { getDashboardSceneProfiler } from 'app/features/dashboard/services/DashboardProfiler'; import { ScopesApiClient } from '../ScopesApiClient'; import { ScopesServiceBase } from '../ScopesServiceBase'; @@ -49,7 +52,10 @@ export class ScopesSelectorService extends ScopesServiceBase { + this.interactionProfiler?.startInteraction('scopeNodeDiscovery'); + const path = getPathOfNode(scopeNodeId, this.state.nodes); const nodeToExpand = treeNodeAtPath(this.state.tree!, path); - if (!nodeToExpand) { - throw new Error(`Node ${scopeNodeId} not found in tree`); - } + try { + if (!nodeToExpand) { + throw new Error(`Node ${scopeNodeId} not found in tree`); + } - if (nodeToExpand.scopeNodeId !== '' && !isNodeExpandable(this.state.nodes[nodeToExpand.scopeNodeId])) { - throw new Error(`Trying to expand node at id ${scopeNodeId} that is not expandable`); - } + if (nodeToExpand.scopeNodeId !== '' && !isNodeExpandable(this.state.nodes[nodeToExpand.scopeNodeId])) { + throw new Error(`Trying to expand node at id ${scopeNodeId} that is not expandable`); + } - // Check if this is first expansion or filtering within existing children - const haveChildrenLoaded = nodeToExpand.children && Object.keys(nodeToExpand.children).length > 0; + // Check if this is first expansion or filtering within existing children + const haveChildrenLoaded = nodeToExpand.children && Object.keys(nodeToExpand.children).length > 0; - if (!nodeToExpand.expanded || nodeToExpand.query !== query || !haveChildrenLoaded) { - const newTree = modifyTreeNodeAtPath(this.state.tree!, path, (treeNode) => { - treeNode.expanded = true; - // Reset query on first expansion, keep it only when filtering within existing children - treeNode.query = ''; - }); - this.updateState({ tree: newTree }); + if (!nodeToExpand.expanded || nodeToExpand.query !== query || !haveChildrenLoaded) { + const newTree = modifyTreeNodeAtPath(this.state.tree!, path, (treeNode) => { + treeNode.expanded = true; + // Reset query on first expansion, keep it only when filtering within existing children + treeNode.query = ''; + }); + this.updateState({ tree: newTree }); - // For API call: only pass query if filtering within existing children - const queryForAPI = haveChildrenLoaded ? query : query === '' ? '' : undefined; - await this.loadNodeChildren(path, nodeToExpand, queryForAPI, haveChildrenLoaded); + // For API call: only pass query if filtering within existing children + const queryForAPI = haveChildrenLoaded ? query : query === '' ? '' : undefined; + await this.loadNodeChildren(path, nodeToExpand, queryForAPI, haveChildrenLoaded); + } + } catch (error) { + throw error; + } finally { + this.interactionProfiler?.stopInteraction(); } };