Skip to content

Commit f384768

Browse files
Enhanced Trace Viewer with Professional UI and VS Code Integration (#84)
* first phase of trace enhancements * updated trace document * phase two of enhancements * phase three of enhancements. current enhancement plan complete. * cleaned up doc
1 parent 16097de commit f384768

File tree

4 files changed

+1582
-131
lines changed

4 files changed

+1582
-131
lines changed

src/panels/resultsPanel.ts

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ import { EXTENSION_URI } from "../extension";
1010
import { StepZenResponse } from "../types";
1111
import { UI } from "../utils/constants";
1212
import { 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
*/
1819
class 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(/resolve\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(/resolve\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};

src/panels/schemaVisualizerPanel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class SchemaVisualizerPanel extends BaseWebviewPanel {
6969
this.panel = this.createWebviewPanel(
7070
UI.SCHEMA_VISUALIZER_VIEW_TYPE,
7171
UI.SCHEMA_VISUALIZER_TITLE,
72-
vscode.ViewColumn.Beside
72+
vscode.ViewColumn.Active
7373
);
7474

7575
// Setup message handling

0 commit comments

Comments
 (0)