@@ -10,13 +10,15 @@ import { EXTENSION_URI } from "../extension";
1010import { StepZenResponse } from "../types" ;
1111import { UI } from "../utils/constants" ;
1212import { BaseWebviewPanel } from "./BaseWebviewPanel" ;
13+ import { services } from "../services" ;
1314
1415/**
1516 * Results panel implementation extending BaseWebviewPanel
1617 * Displays GraphQL response data with tabs for data, errors, debug, and trace
1718 */
1819class ResultsPanel extends BaseWebviewPanel {
1920 private static instance : ResultsPanel | undefined ;
21+ private messageHandler : vscode . Disposable | undefined ;
2022
2123 private constructor ( extensionUri : Uri ) {
2224 super ( extensionUri ) ;
@@ -40,14 +42,136 @@ class ResultsPanel extends BaseWebviewPanel {
4042 this . panel = this . createWebviewPanel (
4143 UI . RESULTS_PANEL_VIEW_TYPE ,
4244 UI . RESULTS_PANEL_TITLE ,
43- vscode . ViewColumn . Beside
45+ vscode . ViewColumn . Active
4446 ) ;
47+
48+ // Setup message handling for VS Code integration
49+ this . setupMessageHandling ( ) ;
4550 }
4651
4752 this . panel . webview . html = this . generateHtml ( this . panel . webview , payload ) ;
4853 this . reveal ( ) ;
4954 }
5055
56+ /**
57+ * Setup message handling for webview-to-extension communication
58+ */
59+ private setupMessageHandling ( ) : void {
60+ if ( ! this . panel ) {
61+ return ;
62+ }
63+
64+ this . messageHandler = this . panel . webview . onDidReceiveMessage ( async ( message ) => {
65+ switch ( message . command ) {
66+ case "navigateToSchema" :
67+ await this . handleNavigateToSchema ( message ) ;
68+ return ;
69+
70+ case "debug-log" :
71+ // Log messages from the webview to the StepZen output channel
72+ services . logger . debug ( `[Results Panel] ${ message . message } ` ) ;
73+ return ;
74+
75+ default :
76+ services . logger . warn ( `Unknown message command: ${ message . command } ` ) ;
77+ }
78+ } ) ;
79+ }
80+
81+ /**
82+ * Handle navigation to schema definition from a span
83+ */
84+ private async handleNavigateToSchema ( message : any ) : Promise < void > {
85+ try {
86+ services . logger . info ( `Navigate to schema requested for span: ${ message . spanName } ` ) ;
87+
88+ // Extract GraphQL field information from span attributes
89+ const fieldPath = message . spanAttributes ?. [ 'graphql.field.path' ] ;
90+
91+ if ( ! fieldPath || ! Array . isArray ( fieldPath ) ) {
92+ vscode . window . showWarningMessage ( "No GraphQL field path found in span data" ) ;
93+ return ;
94+ }
95+
96+ // Use schema index to find the field definition
97+ const fieldIndex = services . schemaIndex . getFieldIndex ( ) ;
98+
99+ // For field paths like ["customer", "orders"], we want to find the "orders" field on the "Customer" type
100+ // The field path contains aliases, but we need to resolve to actual field names
101+ let typeName : string ;
102+ let fieldName : string ;
103+
104+ if ( fieldPath . length === 1 ) {
105+ // Root field like ["customer"] -> Query.customer
106+ typeName = "Query" ;
107+ // For root fields, try to get the actual field name from the span name
108+ // Span names are typically like "resolve Query.customer"
109+ const spanName = message . spanName || '' ;
110+ const resolveMatch = spanName . match ( / r e s o l v e \s + ( \w + ) \. ( \w + ) / ) ;
111+ if ( resolveMatch ) {
112+ fieldName = resolveMatch [ 2 ] ; // Extract actual field name from span name
113+ } else {
114+ fieldName = fieldPath [ 0 ] ; // Fallback to path (might be alias)
115+ }
116+ } else {
117+ // Nested field like ["customer", "orders"] -> Customer.orders
118+ // We need to resolve the type of the parent field
119+ const parentFieldName = fieldPath [ fieldPath . length - 2 ] ;
120+ fieldName = fieldPath [ fieldPath . length - 1 ] ;
121+
122+ // Find the parent field to get its return type
123+ const queryFields = fieldIndex [ "Query" ] || [ ] ;
124+ const parentField = queryFields . find ( f => f . name === parentFieldName ) ;
125+
126+ if ( parentField ) {
127+ // Extract type name from the field type (remove [] and ! modifiers)
128+ typeName = parentField . type . replace ( / [ \[ \] ! ] / g, '' ) ;
129+ } else {
130+ // Fallback: capitalize the parent field name
131+ typeName = parentFieldName . charAt ( 0 ) . toUpperCase ( ) + parentFieldName . slice ( 1 ) ;
132+ }
133+
134+ // For nested fields, also try to get actual field name from span name
135+ const spanName = message . spanName || '' ;
136+ const resolveMatch = spanName . match ( / r e s o l v e \s + ( \w + ) \. ( \w + ) / ) ;
137+ if ( resolveMatch ) {
138+ fieldName = resolveMatch [ 2 ] ; // Use actual field name from span
139+ }
140+ }
141+
142+ // Find the field in the schema index
143+ const typeFields = fieldIndex [ typeName ] || [ ] ;
144+ const targetField = typeFields . find ( f => f . name === fieldName ) ;
145+
146+ if ( targetField && targetField . location ) {
147+ // Navigate to the field definition
148+ const uri = vscode . Uri . file ( targetField . location . uri ) ;
149+ const document = await vscode . workspace . openTextDocument ( uri ) ;
150+ const editor = await vscode . window . showTextDocument ( document ) ;
151+
152+ const position = new vscode . Position (
153+ targetField . location . line ,
154+ targetField . location . character
155+ ) ;
156+
157+ editor . selection = new vscode . Selection ( position , position ) ;
158+ editor . revealRange (
159+ new vscode . Range ( position , position ) ,
160+ vscode . TextEditorRevealType . InCenter
161+ ) ;
162+
163+ services . logger . info ( `Navigated to ${ typeName } .${ fieldName } at ${ targetField . location . uri } :${ targetField . location . line } ` ) ;
164+ } else {
165+ vscode . window . showWarningMessage ( `Could not find schema definition for ${ typeName } .${ fieldName } ` ) ;
166+ services . logger . warn ( `Schema definition not found for ${ typeName } .${ fieldName } ` ) ;
167+ }
168+
169+ } catch ( error ) {
170+ services . logger . error ( "Error navigating to schema" , error ) ;
171+ vscode . window . showErrorMessage ( "Failed to navigate to schema definition" ) ;
172+ }
173+ }
174+
51175 /**
52176 * Clears the results panel by disposing it
53177 */
@@ -56,6 +180,10 @@ class ResultsPanel extends BaseWebviewPanel {
56180 }
57181
58182 protected onDispose ( ) : void {
183+ if ( this . messageHandler ) {
184+ this . messageHandler . dispose ( ) ;
185+ this . messageHandler = undefined ;
186+ }
59187 super . onDispose ( ) ;
60188 ResultsPanel . instance = undefined ;
61189 }
@@ -100,6 +228,9 @@ class ResultsPanel extends BaseWebviewPanel {
100228
101229 <!-- Initialize the panel -->
102230 <script nonce="${ nonce } ">
231+ // Acquire VS Code API for webview communication
232+ const vscode = acquireVsCodeApi();
233+
103234 // Initialize when the DOM is ready
104235 document.addEventListener('DOMContentLoaded', () => {
105236 const payload = ${ payloadJs } ;
0 commit comments