diff --git a/src/commands/openSchemaVisualizer.ts b/src/commands/openSchemaVisualizer.ts index febdcc0..1013497 100644 --- a/src/commands/openSchemaVisualizer.ts +++ b/src/commands/openSchemaVisualizer.ts @@ -8,6 +8,7 @@ import { openSchemaVisualizerPanel } from "../panels/schemaVisualizerPanel"; import { EXTENSION_URI } from "../extension"; import { services } from "../services"; import { handleError } from "../errors"; +import { MESSAGES } from "../utils/constants"; /** * Opens the schema visualizer panel. @@ -20,9 +21,21 @@ export async function openSchemaVisualizer( context: vscode.ExtensionContext, focusedType?: string, ) { + // Check workspace trust + if (!vscode.workspace.isTrusted) { + vscode.window.showWarningMessage(MESSAGES.FEATURE_NOT_AVAILABLE_UNTRUSTED); + return; + } + + // Check if workspace is open + if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) { + vscode.window.showErrorMessage(MESSAGES.NO_WORKSPACE_OPEN); + return; + } + try { services.logger.info( - `Opening Schema Visualizer command${focusedType ? ` focused on type: ${focusedType}` : ""}`, + `Opening Schema Visualizer${focusedType ? ` focused on type: ${focusedType}` : ""}`, ); const extensionUri = EXTENSION_URI || context.extensionUri; diff --git a/src/panels/schemaVisualizerPanel.ts b/src/panels/schemaVisualizerPanel.ts index 3052b57..9d65e09 100644 --- a/src/panels/schemaVisualizerPanel.ts +++ b/src/panels/schemaVisualizerPanel.ts @@ -12,7 +12,6 @@ import type { TypeRelationship, } from "../services/schema/indexer"; import { services } from "../services"; -import { resolveStepZenProjectRoot } from "../utils/stepzenProject"; import * as path from "path"; import * as fs from "fs"; import { MESSAGES, FILE_PATTERNS, UI } from "../utils/constants"; @@ -94,16 +93,18 @@ class SchemaVisualizerPanel extends BaseWebviewPanel { // Build the schema model for visualization const schemaModel = this.buildSchemaModel(); - // Debug logging + // Validate the schema model + const typeCount = Object.keys(schemaModel.types).length; + const fieldCount = Object.keys(schemaModel.fields).length; + const relationshipCount = schemaModel.relationships.length; + services.logger.debug( - `Schema model built: ${Object.keys(schemaModel.types).length} types, ${ - Object.keys(schemaModel.fields).length - } fields with entries, ${schemaModel.relationships.length} relationships`, + `Schema model built: ${typeCount} types, ${fieldCount} field entries, ${relationshipCount} relationships`, ); - if (Object.keys(schemaModel.types).length === 0) { - services.logger.warn("No types found in schema model"); - this.panel.webview.html = this.getNoProjectHtml(); + if (typeCount === 0) { + services.logger.warn("No types found in schema model - showing empty schema message"); + this.panel.webview.html = this.getEmptySchemaHtml(); return; } @@ -119,7 +120,7 @@ class SchemaVisualizerPanel extends BaseWebviewPanel { private setupMessageHandling(): void { if (!this.panel) {return;} - this.messageHandler = this.panel.webview.onDidReceiveMessage((message) => { + this.messageHandler = this.panel.webview.onDidReceiveMessage(async (message) => { switch (message.command) { case "navigateToLocation": const uri = vscode.Uri.file(message.location.uri); @@ -141,6 +142,19 @@ class SchemaVisualizerPanel extends BaseWebviewPanel { // Log messages from the webview to the StepZen output channel services.logger.debug(`[Webview] ${message.message}`); return; + case "refresh-schema": + // Handle schema refresh request + services.logger.info("Schema refresh requested from visualizer"); + try { + // Clear the schema index cache + services.schemaIndex.clearState(); + // Reload the schema data + await this.openWithFocus(); + } catch (error) { + services.logger.error("Failed to refresh schema", error); + vscode.window.showErrorMessage("Failed to refresh schema data. Check the output for details."); + } + return; } }); } @@ -199,7 +213,7 @@ class SchemaVisualizerPanel extends BaseWebviewPanel {
-
Loading schema data...
+
${MESSAGES.SCHEMA_VISUALIZER_LOADING}
@@ -212,10 +226,15 @@ class SchemaVisualizerPanel extends BaseWebviewPanel { * @returns true if schema data was successfully loaded, false otherwise */ private async ensureSchemaDataLoaded(): Promise { + services.logger.debug("Checking if schema data is available..."); + const fieldIndex = services.schemaIndex.getFieldIndex(); + const typeCount = Object.keys(fieldIndex).length; + + services.logger.debug(`Current field index has ${typeCount} types`); // If we already have schema data, return true - if (Object.keys(fieldIndex).length > 0) { + if (typeCount > 0) { services.logger.debug("Using existing schema data"); return true; } @@ -223,17 +242,18 @@ class SchemaVisualizerPanel extends BaseWebviewPanel { services.logger.info("Schema data not found, attempting to load project..."); try { - // Find StepZen project root using the active editor or workspace folders - let projectRoot: string; + // Find StepZen project root using the project resolver service let hintUri: vscode.Uri | undefined; // Get hint URI from active editor if available if (vscode.window.activeTextEditor) { hintUri = vscode.window.activeTextEditor.document.uri; + services.logger.debug(`Using active editor URI: ${hintUri.fsPath}`); } // Otherwise use the first workspace folder else if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) { hintUri = vscode.workspace.workspaceFolders[0].uri; + services.logger.debug(`Using workspace folder URI: ${hintUri.fsPath}`); } // If we have no hint, we can't proceed @@ -242,20 +262,34 @@ class SchemaVisualizerPanel extends BaseWebviewPanel { return false; } - // Use the existing utility to find the project root - projectRoot = await resolveStepZenProjectRoot(hintUri); + // Use the project resolver service to find the project root + const projectRoot = await services.projectResolver.resolveStepZenProjectRoot(hintUri); const indexPath = path.join(projectRoot, FILE_PATTERNS.MAIN_SCHEMA_FILE); + services.logger.debug(`Resolved project root: ${projectRoot}`); + services.logger.debug(`Index path: ${indexPath}`); + // Verify that the index file exists if (!fs.existsSync(indexPath)) { services.logger.warn(`Index file not found at ${indexPath}`); return false; } - // Scan the project + // Scan the project using the schema index service services.logger.info(`Scanning StepZen project at ${indexPath}`); await services.schemaIndex.scan(indexPath); - services.logger.debug("Schema scan completed successfully"); + + // Verify the scan worked + const newFieldIndex = services.schemaIndex.getFieldIndex(); + const newTypeCount = Object.keys(newFieldIndex).length; + + services.logger.debug(`Schema scan completed, found ${newTypeCount} types`); + + if (newTypeCount === 0) { + services.logger.warn("Schema scan completed but no types were found"); + return false; + } + return true; } catch (error) { services.logger.error(`Failed to load schema data`, error); @@ -270,10 +304,16 @@ class SchemaVisualizerPanel extends BaseWebviewPanel { * @returns A complete schema model for visualization */ private buildSchemaModel(): SchemaVisualizerModel { + services.logger.debug("Building schema model for visualization..."); + const fieldIndex = services.schemaIndex.getFieldIndex(); const typeDirectives = services.schemaIndex.getTypeDirectives(); const relationships = services.schemaIndex.getTypeRelationships(); + services.logger.debug(`Field index has ${Object.keys(fieldIndex).length} types`); + services.logger.debug(`Type directives has ${Object.keys(typeDirectives).length} entries`); + services.logger.debug(`Found ${relationships.length} relationships`); + const model: SchemaVisualizerModel = { types: {}, fields: fieldIndex, @@ -291,11 +331,83 @@ class SchemaVisualizerPanel extends BaseWebviewPanel { directives: typeDirectives[typeName] || [], location, }; + + services.logger.debug(`Added type ${typeName} with ${fields.length} fields`); } + services.logger.debug(`Schema model built with ${Object.keys(model.types).length} types`); return model; } + /** + * Generates HTML for when the schema is empty (no types found) + * + * @returns HTML string for the empty schema message + */ + private getEmptySchemaHtml(): string { + const nonce = this.nonce(); + return ` + + + + + + + StepZen Schema Visualizer + + + +
+

${MESSAGES.SCHEMA_VISUALIZER_NO_TYPES_FOUND}

+

+ ${MESSAGES.SCHEMA_VISUALIZER_NO_TYPES_DESCRIPTION} +

+
+ Suggestions: +
    +
  • Make sure your schema files contain type definitions
  • +
  • Check that your index.graphql file properly references other schema files
  • +
  • Verify that the @sdl directive includes all necessary files
  • +
  • Try refreshing the visualizer after making changes
  • +
+
+
+ + + `; + } + /** * Generates HTML for an error message when no StepZen project is found * @@ -345,6 +457,17 @@ class SchemaVisualizerPanel extends BaseWebviewPanel { `; } + /** + * Override CSP to allow inline styles for dynamic content + * + * @param webview The webview to generate CSP for + * @param nonce The nonce value to include in the CSP + * @returns CSP header string + */ + protected csp(webview: vscode.Webview, nonce: string): string { + return `default-src 'none'; img-src ${webview.cspSource}; style-src ${webview.cspSource} 'unsafe-inline'; script-src 'nonce-${nonce}' ${webview.cspSource};`; + } + /** * Generates the HTML for the schema visualizer webview panel * Includes all necessary scripts, styles, and data for the visualization @@ -423,6 +546,7 @@ class SchemaVisualizerPanel extends BaseWebviewPanel { +
@@ -435,6 +559,9 @@ class SchemaVisualizerPanel extends BaseWebviewPanel {