Skip to content

Add delete option for servers and instances in VS Code extension#82

Draft
Copilot wants to merge 3 commits intomainfrom
copilot/add-delete-option-for-servers
Draft

Add delete option for servers and instances in VS Code extension#82
Copilot wants to merge 3 commits intomainfrom
copilot/add-delete-option-for-servers

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 14, 2026

The Drasi VS Code extension had no way to remove servers or instances once added — only "Add instance", "Add server", and "Edit server URL" were available in the context menu.

Changes

VS Code Extension

  • ConnectionRegistry: Added deleteConnection(id) — removes the connection from settings and falls back to the next available connection if the deleted one was active
  • DrasiClient: Added deleteInstance(id) — calls DELETE /api/v1/instances/:instanceId
  • DrasiExplorer: Added deleteConnection and deleteInstance command handlers, both with confirmation dialogs before acting
  • package.json: Registered drasi.connection.delete and drasi.instance.delete commands; wired into view/item/context under 3_danger group for connectionNode, connectionCurrentNode, and instanceNode

REST API

  • DELETE /api/v1/instances/:instanceId: New endpoint that removes an instance from the registry, respects read-only mode, and cleans up persistence
  • ConfigPersistence.unregister_instance: Clears all associated source, reaction, query, and instance configs on deletion
  • OpenAPI spec updated to include the new endpoint

Tests

Added openapi_test.rs cases covering: endpoint presence in OpenAPI spec, successful deletion, not-found error response, and read-only mode rejection.

Copilot AI and others added 2 commits April 14, 2026 22:13
- Add DELETE /api/v1/instances/:instanceId REST endpoint (shared handler, v1 handler, route, openapi)
- Add unregister_instance to ConfigPersistence for cleanup on deletion
- Add deleteInstance method to DrasiClient
- Add deleteConnection method to ConnectionRegistry
- Add drasi.connection.delete and drasi.instance.delete command handlers in DrasiExplorer
- Register new commands and context menu items in package.json

Agent-Logs-Url: https://github.com/drasi-project/drasi-server/sessions/0ed126f2-c92d-47c6-9cf4-063d3e68af3c

Co-authored-by: NValsan <15096090+NValsan@users.noreply.github.com>
- Add tests: test_openapi_has_delete_instance_endpoint, test_delete_instance_succeeds, test_delete_instance_not_found_returns_error, test_delete_instance_read_only_rejected
- Remove unnecessary explicit drop() calls in unregister_instance (use block scoping instead)

Agent-Logs-Url: https://github.com/drasi-project/drasi-server/sessions/0ed126f2-c92d-47c6-9cf4-063d3e68af3c

Co-authored-by: NValsan <15096090+NValsan@users.noreply.github.com>
Copilot AI changed the title [WIP] Add delete option for servers in Drasi extension Add delete option for servers and instances in VS Code extension Apr 14, 2026
Copilot AI requested a review from NValsan April 14, 2026 22:32
@danielgerlag danielgerlag requested a review from Copilot April 28, 2026 22:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds delete functionality for servers and instances across the Drasi VS Code extension and Drasi Server REST API, including persistence cleanup and OpenAPI/test coverage.

Changes:

  • Add DELETE /api/v1/instances/{instanceId} endpoint and wire it into the v1 router + OpenAPI.
  • Implement persistence cleanup for deleted instances (instance + associated source/reaction/query configs).
  • Add VS Code commands/UI for deleting servers and instances, plus OpenAPI integration tests for the new endpoint.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/openapi_test.rs Adds OpenAPI/spec + functional tests for instance deletion behavior.
src/persistence.rs Adds unregister_instance to remove per-instance persisted config data.
src/api/v1/routes.rs Registers the instance delete route.
src/api/v1/openapi.rs Includes the new handler in the OpenAPI paths list.
src/api/v1/mod.rs Updates endpoint structure docs to mention delete.
src/api/v1/handlers.rs Adds OpenAPI wrapper handler for delete instance.
src/api/shared/handlers.rs Implements shared delete-instance logic (registry removal + persistence cleanup).
dev-tools/vscode/drasi-server/src/sdk/config.ts Adds ConnectionRegistry.deleteConnection.
dev-tools/vscode/drasi-server/src/drasi-explorer.ts Registers delete commands and adds confirmation/progress flows.
dev-tools/vscode/drasi-server/src/drasi-client.ts Adds DrasiClient.deleteInstance.
dev-tools/vscode/drasi-server/package.json Registers new commands + context menu entries.
dev-tools/vscode/drasi-server/package-lock.json Updates extension lockfile metadata.
Files not reviewed (1)
  • dev-tools/vscode/drasi-server/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +335 to +347
match registry.remove(&instance_id).await {
Some(_) => {
if let Some(persistence) = &config_persistence {
persistence.unregister_instance(&instance_id).await;
}

persist_after_operation(&config_persistence, "deleting instance").await;

log::info!("Instance '{instance_id}' deleted successfully");
Json(ApiResponse::success(StatusResponse {
message: format!("Instance '{instance_id}' deleted successfully"),
}))
}
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete_instance removes the instance from InstanceRegistry but never stops the underlying DrasiLib (the server shutdown path explicitly calls core.stop().await). This can leave background tasks/resources running after deletion. Consider calling core.stop().await (and handling/logging any error) before/while removing it from the registry so deletion actually tears down the instance.

Copilot uses AI. Check for mistakes.
Comment thread src/api/v1/handlers.rs
),
responses(
(status = 200, description = "Instance deleted successfully", body = ApiResponse),
(status = 404, description = "Instance not found"),
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OpenAPI annotation documents a 404 response for "Instance not found", but the handler always returns Json<ApiResponse<...>> (HTTP 200) with success: false on not-found (see shared delete_instance). Either update the implementation to return an actual 404 status, or update the OpenAPI responses(...) to match the real behavior so clients aren't misled.

Suggested change
(status = 404, description = "Instance not found"),
(status = 200, description = "Instance not found; returned as `success: false` in the ApiResponse body", body = ApiResponse),

Copilot uses AI. Check for mistakes.
Comment on lines +542 to +555
await vscode.window.withProgress({
title: `Deleting instance ${instanceNode.instance.id}`,
location: vscode.ProgressLocation.Notification,
}, async (progress) => {
progress.report({ message: 'Deleting...' });
try {
await this.drasiClient.deleteInstance(instanceNode.instance.id);
vscode.window.showInformationMessage(`Instance '${instanceNode.instance.id}' deleted`);
} catch (err) {
vscode.window.showErrorMessage(`Error deleting instance: ${err}`);
}
});
this.refresh();
}
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After deleting an instance, the extension may still have that instance set as the current instance (stored on the current connection). DrasiClient.getCurrentInstanceId() will then keep using the deleted ID and subsequent actions will fail until the user manually selects another instance. Consider clearing instanceId (or selecting the first remaining instance) when the deleted instance matches the current connection's instanceId.

Copilot uses AI. Check for mistakes.
Comment on lines +73 to +78
async deleteInstance(instanceId: string): Promise<void> {
const res = await this.delete<ApiResponse<any>>(`${this.apiBase}/instances/${instanceId}`);
if (!res.data?.success) {
throw new Error(res.data?.error ?? res.statusText);
}
}
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instanceId is interpolated directly into the URL path. If instance IDs can contain characters that need escaping (spaces, #, /, etc.), requests can break or be routed incorrectly. Consider URL-encoding the path segment (e.g., encodeURIComponent(instanceId)) when constructing the DELETE URL.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@danielgerlag danielgerlag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together — the feature itself is well-scoped and the VS Code UX (confirmation dialogs, QuickPick fallback, progress notifications) is solid. The persistence cleanup in unregister_instance is clean too. There are a few issues that need addressing before this merges, mostly around the instance lifecycle and the API's error handling conventions.


Changes requested

🔴 Critical: Instance not shut down before removal

When registry.remove() is called, it simply drops the Arc<DrasiLib> from the HashMap. DrasiLib has no impl Drop, so the Tokio tasks backing all sources, queries, and reactions in the instance keep running — network connections stay open, the internal graph update loop is never aborted, and resources aren't cleanly released.

DrasiLib has a shutdown() method designed exactly for this case — it stops all components and aborts the graph update loop, performing a full teardown. Please call it on the removed instance before proceeding with persistence cleanup:

match registry.remove(&instance_id).await {
    Some(instance) => {
        // Cleanly shut down all sources, queries, reactions, and internal tasks
        if let Err(e) = instance.shutdown().await {
            warn!("Errors while shutting down instance '{instance_id}': {e}");
        }
        // ... persistence cleanup ...
    }
    None => { /* not found */ }
}

🔴 Critical: Handler returns 200 OK for all error cases

The delete_instance handler (both in src/api/shared/handlers.rs and src/api/v1/handlers.rs) returns Json<ApiResponse<StatusResponse>> unconditionally. This means "not found" and "read-only mode" both come back as HTTP 200 with success: false in the body, which violates the error handling architecture documented in CLAUDE.md and implemented throughout the rest of the codebase.

The established pattern is Result<Json<ApiResponse<T>>, ErrorResponse>ErrorResponse implements IntoResponse and sets the correct HTTP status code automatically. Please align with this:

pub async fn delete_instance(...) -> Result<Json<ApiResponse<StatusResponse>>, ErrorResponse> {
    if *read_only {
        return Err(ErrorResponse::new(error_codes::CONFIG_READ_ONLY, "Server is in read-only mode"));
    }
    match registry.remove(&instance_id).await {
        Some(instance) => {
            instance.shutdown().await.ok();
            persist_after_operation(&config_persistence, "deleting instance").await?;
            Ok(Json(ApiResponse::success(StatusResponse { ... })))
        }
        None => Err(ErrorResponse::new(error_codes::INSTANCE_NOT_FOUND, format!("Instance '{instance_id}' not found"))),
    }
}

Also note: the persist_after_operation return value is currently ignored. It returns Result<(), ErrorResponse> and should be ?-propagated as shown above.


🟠 High: Tests validate the wrong HTTP status codes

The tests test_delete_instance_not_found_returns_error and test_delete_instance_read_only_rejected both assert StatusCode::OK for error responses (the comments even say "Response should be 200 with error body"). Once the handler return type is corrected, these should assert the proper codes — StatusCode::NOT_FOUND (404) for the missing instance case, and the appropriate code for read-only mode.


🟡 Medium: VS Code tree refreshes even when deletion fails

In drasi-explorer.ts, this.refresh() is called unconditionally after withProgress completes, regardless of whether the API call succeeded or threw. This means the tree view refreshes even when an error message is shown — which could be confusing since the instance is still there. Moving this.refresh() inside the try block (after the successful deleteInstance call) would make the behaviour clearer.


Happy to discuss any of these — let me know if anything needs clarification!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Drasi Server extension does not have a delete option

4 participants