Add configure connection delete subcommand#25
Conversation
Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
configure connection delete subcommand
There was a problem hiding this comment.
Pull request overview
Adds a new gh devlake configure connection delete subcommand to remove DevLake plugin connections (interactive selection or --plugin/--id flags), wiring it through a new DevLake API client delete method and documenting the command.
Changes:
- Add
Client.DeleteConnection(plugin, id)that callsDELETE /plugins/{plugin}/connections/{id}. - Introduce
configure connection deletecommand with interactive selection + confirmation and state-file update. - Add basic command registration/validation tests and README documentation.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| internal/devlake/client.go | Adds DeleteConnection API call for deleting connections by plugin + ID. |
| cmd/configure_connection_delete.go | Implements the new configure connection delete subcommand (interactive + flag mode), confirmation flow, and state update. |
| cmd/configure_connection_delete_test.go | Adds tests for subcommand registration and unknown-plugin validation. |
| README.md | Documents the new delete command usage and behavior. |
| plugin := connDeletePlugin | ||
| connID := connDeleteID | ||
|
|
||
| if plugin == "" || connID == 0 { |
There was a problem hiding this comment.
The interactive fallback currently triggers when either --plugin or --id is missing (if plugin == "" || connID == 0). This means a partially-specified flag invocation (e.g. --id without --plugin) unexpectedly drops into interactive mode instead of failing fast, which contradicts the documented/required “flag mode requires both” behavior. Consider treating it as flag mode when either flag is set (use cmd.Flags().Changed(...)) and return an error unless both values are provided; reserve interactive mode only when neither flag is set.
| if plugin == "" || connID == 0 { | |
| pluginFlagSet := cmd.Flags().Changed("plugin") | |
| idFlagSet := cmd.Flags().Changed("id") | |
| if pluginFlagSet || idFlagSet { | |
| // Flag mode: if either flag is set, require both to be provided and valid | |
| if !pluginFlagSet || !idFlagSet || plugin == "" || connID == 0 { | |
| return fmt.Errorf("both --plugin and --id must be provided together") | |
| } | |
| } else { |
There was a problem hiding this comment.
Fixed in c4307f4. Flag-mode detection now uses cmd.Flags().Changed("plugin") / cmd.Flags().Changed("id") early (before discovery), so partial invocations fail immediately with "both --plugin and --id must be provided together". Interactive mode is only entered when neither flag is set.
| updated = append(updated, c) | ||
| } | ||
| if err := devlake.UpdateConnections(statePath, state, updated); err != nil { | ||
| fmt.Fprintf(os.Stderr, "⚠️ Could not update state file: %v\n", err) |
There was a problem hiding this comment.
Per .github/instructions/terminal-output.instructions.md, emoji-prefixed lines should start with a leading blank line (\n) so they read as distinct steps. This warning is emitted without a preceding blank line, so it can jam against the previous output; prepend a newline (or add an extra fmt.Println() before it) to keep spacing consistent.
| fmt.Fprintf(os.Stderr, "⚠️ Could not update state file: %v\n", err) | |
| fmt.Fprintf(os.Stderr, "\n⚠️ Could not update state file: %v\n", err) |
There was a problem hiding this comment.
Fixed in c4307f4 — prepended \n to the stderr warning: fmt.Fprintf(os.Stderr, "\n⚠️ Could not update state file: %v\n", err).
| body, _ := io.ReadAll(resp.Body) | ||
| if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { | ||
| return fmt.Errorf("delete connection returned %d: %s", resp.StatusCode, body) | ||
| } |
There was a problem hiding this comment.
DeleteConnection returns a generic status/body error for all non-200/204 responses. The UX requirement mentions a graceful error when the connection ID doesn’t exist; consider special-casing 404 to return a clearer “not found” error (including plugin + id), and consider including the HTTP method/path in the generic error (e.g. DELETE /plugins/{plugin}/connections/{id}) to make troubleshooting easier.
There was a problem hiding this comment.
Fixed in c4307f4. DeleteConnection now special-cases 404 with "connection not found: plugin=%s id=%d" and includes the full path in the generic error: "DELETE /plugins/%s/connections/%d returned %d: %s".
| if !strings.Contains(err.Error(), "unknown plugin") { | ||
| t.Errorf("unexpected error message: %v", err) | ||
| } | ||
| } |
There was a problem hiding this comment.
The tests cover subcommand registration and unknown-plugin validation, but don’t exercise the new command’s required flag-mode behavior (e.g. erroring when only one of --plugin / --id is provided, and accepting when both are provided). Adding a test for these cases will help prevent regressions in the interactive-vs-flag resolution logic.
| } | |
| } | |
| func TestRunDeleteConnection_FlagsPluginOnly(t *testing.T) { | |
| origPlugin := connDeletePlugin | |
| origID := connDeleteID | |
| t.Cleanup(func() { | |
| connDeletePlugin = origPlugin | |
| connDeleteID = origID | |
| }) | |
| connDeletePlugin = "" | |
| connDeleteID = 0 | |
| cmd := &cobra.Command{RunE: runDeleteConnection} | |
| cmd.Flags().StringVar(&connDeletePlugin, "plugin", "", "") | |
| cmd.Flags().IntVar(&connDeleteID, "id", 0, "") | |
| _ = cmd.Flags().Set("plugin", "gitlab") | |
| err := runDeleteConnection(cmd, nil) | |
| if err == nil { | |
| t.Fatal("expected error when only --plugin is provided, got nil") | |
| } | |
| } | |
| func TestRunDeleteConnection_FlagsIDOnly(t *testing.T) { | |
| origPlugin := connDeletePlugin | |
| origID := connDeleteID | |
| t.Cleanup(func() { | |
| connDeletePlugin = origPlugin | |
| connDeleteID = origID | |
| }) | |
| connDeletePlugin = "" | |
| connDeleteID = 0 | |
| cmd := &cobra.Command{RunE: runDeleteConnection} | |
| cmd.Flags().StringVar(&connDeletePlugin, "plugin", "", "") | |
| cmd.Flags().IntVar(&connDeleteID, "id", 0, "") | |
| _ = cmd.Flags().Set("id", "1") | |
| err := runDeleteConnection(cmd, nil) | |
| if err == nil { | |
| t.Fatal("expected error when only --id is provided, got nil") | |
| } | |
| } |
There was a problem hiding this comment.
Added in c4307f4: TestRunDeleteConnection_PluginOnlyNoID and TestRunDeleteConnection_IDOnlyNoPlugin both verify that partial flag invocations return "both --plugin and --id must be provided together".
|
@copilot apply changes based on the comments in this thread |
…tests Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
DeleteConnection(plugin string, connID int) errormethod tointernal/devlake/client.gocmd/configure_connection_delete.gowithdeletesubcommand underconfigureConnectionsCmd--plugin+--idrequired (usecmd.Flags().Changedto detect partial invocations and fail fast)\nprefix before stderr warning on state file update failurecmd/configure_connection_delete_test.gowith tests including partial flag mode casesREADME.mdwith new command documentationOriginal prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.