Skip to content

Commit

Permalink
[CLI-2577] adding feedback command and tests (#2074)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin-wu24 committed Jul 27, 2023
1 parent 10e1993 commit 74dcd8a
Show file tree
Hide file tree
Showing 13 changed files with 151 additions and 17 deletions.
2 changes: 2 additions & 0 deletions internal/cmd/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/confluentinc/cli/internal/cmd/connect"
"github.com/confluentinc/cli/internal/cmd/context"
"github.com/confluentinc/cli/internal/cmd/environment"
"github.com/confluentinc/cli/internal/cmd/feedback"
"github.com/confluentinc/cli/internal/cmd/flink"
"github.com/confluentinc/cli/internal/cmd/iam"
"github.com/confluentinc/cli/internal/cmd/kafka"
Expand Down Expand Up @@ -113,6 +114,7 @@ func NewConfluentCommand(cfg *v1.Config) *cobra.Command {
cmd.AddCommand(context.New(prerunner, flagResolver))
cmd.AddCommand(connect.New(cfg, prerunner))
cmd.AddCommand(environment.New(prerunner))
cmd.AddCommand(feedback.New(prerunner))
cmd.AddCommand(iam.New(cfg, prerunner))
cmd.AddCommand(kafka.New(cfg, prerunner))
cmd.AddCommand(ksql.New(cfg, prerunner))
Expand Down
71 changes: 71 additions & 0 deletions internal/cmd/feedback/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package feedback

import (
"fmt"
"strings"

"github.com/spf13/cobra"

cliv1 "github.com/confluentinc/ccloud-sdk-go-v2/cli/v1"

pcmd "github.com/confluentinc/cli/internal/pkg/cmd"
"github.com/confluentinc/cli/internal/pkg/form"
"github.com/confluentinc/cli/internal/pkg/output"
pversion "github.com/confluentinc/cli/internal/pkg/version"
)

type command struct {
*pcmd.AuthenticatedCLICommand
}

func New(prerunner pcmd.PreRunner) *cobra.Command {
cmd := &cobra.Command{
Use: "feedback",
Short: fmt.Sprintf("Submit feedback for the %s.", pversion.FullCLIName),
Args: cobra.NoArgs,
Annotations: map[string]string{pcmd.RunRequirement: pcmd.RequireNonAPIKeyCloudLogin},
}

c := &command{AuthenticatedCLICommand: pcmd.NewAuthenticatedCLICommand(cmd, prerunner)}
cmd.RunE = c.feedback

return cmd
}

func (c *command) feedback(_ *cobra.Command, _ []string) error {
feedback, err := getFeedback(form.NewPrompt())
if err != nil {
return err
}
if feedback != "" {
feedbackReq := cliv1.CliV1Feedback{Content: cliv1.PtrString(feedback)}
if err := c.V2Client.CreateCliFeedback(feedbackReq); err != nil {
return err
}
output.Println("Thanks for your feedback.")
}
return nil
}

func getFeedback(prompt form.Prompt) (string, error) {
f := form.New(
form.Field{
ID: "feedback",
Prompt: "Enter feedback",
},
form.Field{
ID: "proceed",
Prompt: "Please confirm that your feedback does not contain any sensitive information",
IsYesOrNo: true,
},
)
if err := f.Prompt(prompt); err != nil {
return "", err
}
feedback := strings.TrimSpace(f.Responses["feedback"].(string))
if !f.Responses["proceed"].(bool) {
output.Println("Your feedback was not submitted.")
return "", nil
}
return feedback, nil
}
5 changes: 5 additions & 0 deletions internal/pkg/ccloudv2/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ func (c *Client) cliApiContext() context.Context {
return context.WithValue(context.Background(), cliv1.ContextAccessToken, c.AuthToken)
}

func (c *Client) CreateCliFeedback(feedback cliv1.CliV1Feedback) error {
httpResp, err := c.CliClient.FeedbacksCliV1Api.CreateCliV1Feedback(c.cliApiContext()).CliV1Feedback(feedback).Execute()
return errors.CatchCCloudV2Error(err, httpResp)
}

func (c *Client) CreateCliUsage(usage cliv1.CliV1Usage) error {
httpResp, err := c.CliClient.UsagesCliV1Api.CreateCliV1Usage(c.cliApiContext()).CliV1Usage(usage).Execute()
return errors.CatchCCloudV2Error(err, httpResp)
Expand Down
14 changes: 14 additions & 0 deletions test/feedback_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package test

func (s *CLITestSuite) TestFeedback() {
tests := []CLITest{
{args: "feedback", fixture: "feedback/no-confirm.golden", input: "This CLI is great!\nn\n"},
{args: "feedback", fixture: "feedback/received.golden", input: "This CLI is great!\ny\n"},
{args: "feedback", exitCode: 1, fixture: "feedback/too-long.golden", input: "Lorem ipsum dolor sit amet. Qui amet molestiae eum eaque perferendis\ny\n"},
}

for _, test := range tests {
test.login = "cloud"
s.runIntegrationTest(test)
}
}
9 changes: 9 additions & 0 deletions test/fixtures/output/feedback/help.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Submit feedback for the Confluent CLI.

Usage:
confluent feedback [flags]

Global Flags:
-h, --help Show help for this command.
--unsafe-trace Equivalent to -vvvv, but also log HTTP requests and responses which might contain plaintext secrets.
-v, --verbose count Increase verbosity (-v for warn, -vv for info, -vvv for debug, -vvvv for trace).
1 change: 1 addition & 0 deletions test/fixtures/output/feedback/no-confirm.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Enter feedback: Please confirm that your feedback does not contain any sensitive information (y/n): Your feedback was not submitted.
1 change: 1 addition & 0 deletions test/fixtures/output/feedback/received.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Enter feedback: Please confirm that your feedback does not contain any sensitive information (y/n): Thanks for your feedback.
1 change: 1 addition & 0 deletions test/fixtures/output/feedback/too-long.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Enter feedback: Please confirm that your feedback does not contain any sensitive information (y/n): Error: feedback exceeds the maximum length: 403 Forbidden
1 change: 1 addition & 0 deletions test/fixtures/output/help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Available Commands:
connect Manage Kafka Connect.
context Manage CLI configuration contexts.
environment Manage and select Confluent Cloud environments.
feedback Submit feedback for the Confluent CLI.
flink Manage Apache Flink.
help Help about any command
iam Manage RBAC and IAM permissions.
Expand Down
1 change: 1 addition & 0 deletions test/test-server/ccloudv2_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var ccloudV2Routes = []route{
{"/cdx/v1/provider-shares/{id}", handleStreamSharingProviderShare},
{"/cdx/v1/provider-shares/{id}:resend", handleStreamSharingResendInvite},
{"/cdx/v1/shared-tokens:redeem", handleStreamSharingRedeemToken},
{"/cli/v1/feedbacks", handleFeedbacks},
{"/cmk/v2/clusters", handleCmkClusters},
{"/cmk/v2/clusters/{id}", handleCmkCluster},
{"/connect/v1/environments/{env}/clusters/{clusters}/connector-plugins", handlePlugins},
Expand Down
27 changes: 27 additions & 0 deletions test/test-server/cli_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package testserver

import (
"encoding/json"
"net/http"
"testing"

"github.com/stretchr/testify/require"

cliv1 "github.com/confluentinc/ccloud-sdk-go-v2/cli/v1"
)

// Handler for: /cli/v1/feedbacks
func handleFeedbacks(t *testing.T) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
req := new(cliv1.CliV1Feedback)
err := json.NewDecoder(r.Body).Decode(req)
require.NoError(t, err)
if len(*req.Content) > 20 {
w.WriteHeader(http.StatusForbidden)
err = writeErrorJson(w, "feedback exceeds the maximum length")
require.NoError(t, err)
} else {
w.WriteHeader(http.StatusNoContent)
}
}
}
3 changes: 1 addition & 2 deletions test/test-server/cmk_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package testserver

import (
"encoding/json"
"io"
"net/http"
"testing"

Expand Down Expand Up @@ -299,7 +298,7 @@ func handleCmkKafkaDedicatedClusterShrinkMulti(t *testing.T) http.HandlerFunc {
require.NoError(t, err)
case http.MethodPatch:
w.WriteHeader(http.StatusBadRequest)
_, err := io.WriteString(w, badRequestErrMsg)
err := writeErrorJson(w, "Bad Request")
require.NoError(t, err)
}
}
Expand Down
32 changes: 17 additions & 15 deletions test/test-server/utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package testserver

import (
"encoding/json"
"fmt"
"io"
"net/http"
Expand All @@ -17,13 +18,9 @@ import (
mdsv2 "github.com/confluentinc/ccloud-sdk-go-v2/mds/v2"
)

var (
serviceAccountInvalidErrMsg = `{"errors":[{"status":"403","detail":"service account is not valid"}]}`
roleNameInvalidErrMsg = `{"status_code":400,"message":"Invalid role name : %s","type":"INVALID REQUEST DATA"}`
resourceNotFoundErrMsg = `{"errors":[{"detail":"resource not found"}], "message":"resource not found"}`
badRequestErrMsg = `{"errors":[{"status":"400","detail":"Bad Request"}]}`
userConflictErrMsg = `{"errors":[{"detail":"This user already exists within the Organization"}]}`
)
type ErrorJson struct {
Message string `json:"message"`
}

type ApiKeyListV2 []apikeysv2.IamV2ApiKey

Expand Down Expand Up @@ -239,28 +236,33 @@ func fillByokStoreV1() map[string]*byokv1.ByokV1Key {
return byokStoreV1
}

func writeErrorJson(w http.ResponseWriter, message string) error {
errorJson, err := json.Marshal(ErrorJson{Message: message})
if err != nil {
return err
}
_, err = io.WriteString(w, string(errorJson))
return err
}

func writeServiceAccountInvalidError(w http.ResponseWriter) error {
w.WriteHeader(http.StatusForbidden)
_, err := io.WriteString(w, serviceAccountInvalidErrMsg)
return err
return writeErrorJson(w, "service account is not valid")
}

func writeResourceNotFoundError(w http.ResponseWriter) error {
w.WriteHeader(http.StatusForbidden)
_, err := io.WriteString(w, resourceNotFoundErrMsg)
return err
return writeErrorJson(w, "resource not found")
}

func writeInvalidRoleNameError(w http.ResponseWriter, roleName string) error {
w.WriteHeader(http.StatusBadRequest)
_, err := io.WriteString(w, fmt.Sprintf(roleNameInvalidErrMsg, roleName))
return err
return writeErrorJson(w, fmt.Sprintf("Invalid role name : %s", roleName))
}

func writeUserConflictError(w http.ResponseWriter) error {
w.WriteHeader(http.StatusConflict)
_, err := io.WriteString(w, userConflictErrMsg)
return err
return writeErrorJson(w, "This user already exists within the Organization")
}

func getCmkBasicDescribeCluster(id string, name string) *cmkv2.CmkV2Cluster {
Expand Down

0 comments on commit 74dcd8a

Please sign in to comment.