From 52210b16ac67defcc9887ef33e729ceff51873b7 Mon Sep 17 00:00:00 2001 From: Rob Galanakis Date: Mon, 5 Feb 2024 08:33:04 -0800 Subject: [PATCH] Support for saved queries --- client/db.go | 9 +-- client/entities.go | 7 -- client/saved_queries.go | 70 ++++++++++++++++ cmd/cmd.go | 1 + cmd/cmd_db.go | 2 +- cmd/cmd_flags.go | 4 + cmd/cmd_saved_queries.go | 171 +++++++++++++++++++++++++++++++++++++++ types/types.go | 9 +++ 8 files changed, 257 insertions(+), 16 deletions(-) create mode 100644 client/saved_queries.go create mode 100644 cmd/cmd_saved_queries.go diff --git a/client/db.go b/client/db.go index 3bf5306..52f3967 100644 --- a/client/db.go +++ b/client/db.go @@ -2,7 +2,6 @@ package client import ( "context" - "encoding/json" "github.com/webhookdb/webhookdb-cli/types" ) @@ -34,13 +33,7 @@ type DbSqlInput struct { Query string `json:"query"` } -type DbSqlOutput struct { - // Use RawMessage to avoid deserializing the JSON right away. - // This allows us to render maps and certain other types verbatim. - Rows [][]json.RawMessage `json:"rows"` - Headers []string `json:"headers"` - MaxRowsReached bool `json:"max_rows_reached"` -} +type DbSqlOutput = types.RunQueryOutput func DbSql(c context.Context, auth Auth, input DbSqlInput) (out DbSqlOutput, err error) { err = makeRequest(c, POST, auth, input, &out, "/v1/db/%v/sql", input.OrgIdentifier) diff --git a/client/entities.go b/client/entities.go index e91e561..a32eb0d 100644 --- a/client/entities.go +++ b/client/entities.go @@ -17,10 +17,3 @@ type ServiceIntegrationEntity struct { ServiceName string `json:"service_name"` TableName string `json:"table_name"` } - -type WebhookSubscriptionEntity struct { - OpaqueId string `json:"opaque_id"` - DeliverToUrl string `json:"deliver_to_url"` - Organization types.Organization `json:"organization"` - ServiceIntegration ServiceIntegrationEntity `json:"service_integration"` -} diff --git a/client/saved_queries.go b/client/saved_queries.go new file mode 100644 index 0000000..c81a5e2 --- /dev/null +++ b/client/saved_queries.go @@ -0,0 +1,70 @@ +package client + +import ( + "context" + "github.com/webhookdb/webhookdb-cli/formatting" + "github.com/webhookdb/webhookdb-cli/types" +) + +type SavedQueryListInput struct { + OrgIdentifier types.OrgIdentifier `json:"-"` +} + +func SavedQueryList(c context.Context, auth Auth, input SavedQueryListInput) (out types.CollectionResponse, err error) { + err = makeRequest(c, GET, auth, nil, &out, "/v1/organizations/%v/saved_queries", input.OrgIdentifier) + return +} + +type SavedQueryCreateInput struct { + OrgIdentifier types.OrgIdentifier `json:"-"` + Description string `json:"description"` + Sql string `json:"sql"` + Public bool `json:"public"` +} + +func SavedQueryCreate(c context.Context, auth Auth, input SavedQueryCreateInput) (out types.MessageResponse, err error) { + err = makeRequest(c, POST, auth, input, &out, "/v1/organizations/%s/saved_queries/create", input.OrgIdentifier) + return +} + +type SavedQueryIdentifierInput struct { + OrgIdentifier types.OrgIdentifier `json:"-"` + Identifier string `json:"-"` +} + +func SavedQueryDelete(c context.Context, auth Auth, input SavedQueryIdentifierInput) (out types.MessageResponse, err error) { + err = makeRequest(c, POST, auth, input, &out, "/v1/organizations/%v/saved_queries/%v/delete", input.OrgIdentifier, input.Identifier) + return +} + +type SavedQueryUpdateInput struct { + OrgIdentifier types.OrgIdentifier `json:"-"` + Identifier string `json:"-"` + Field string `json:"field"` + Value string `json:"value"` +} + +func SavedQueryUpdate(c context.Context, auth Auth, input SavedQueryUpdateInput) (out types.MessageResponse, err error) { + err = makeRequest(c, POST, auth, input, &out, "/v1/organizations/%v/saved_queries/%v/update", input.OrgIdentifier, input.Identifier) + return +} + +type SavedQueryInfoInput struct { + OrgIdentifier types.OrgIdentifier `json:"-"` + Identifier string `json:"-"` + Field string `json:"field"` +} + +type SavedQueryInfoOutput struct { + Blocks formatting.Blocks `json:"blocks"` +} + +func SavedQueryInfo(c context.Context, auth Auth, input SavedQueryInfoInput) (out SavedQueryInfoOutput, err error) { + err = makeRequest(c, POST, auth, input, &out, "/v1/organizations/%v/saved_queries/%v/info", input.OrgIdentifier, input.Identifier) + return +} + +func SavedQueryRun(c context.Context, auth Auth, input SavedQueryIdentifierInput) (out types.RunQueryOutput, err error) { + err = makeRequest(c, GET, auth, input, &out, "/v1/organizations/%v/saved_queries/%v/run", input.OrgIdentifier, input.Identifier) + return +} diff --git a/cmd/cmd.go b/cmd/cmd.go index 73550fd..27d4b35 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -45,6 +45,7 @@ Documentation: integrationsCmd, organizationsCmd, replayCmd, + savedQueriesCmd, servicesCmd, subscriptionsCmd, syncCmd(dbSyncType), diff --git a/cmd/cmd_db.go b/cmd/cmd_db.go index 5be8f83..24d6b29 100644 --- a/cmd/cmd_db.go +++ b/cmd/cmd_db.go @@ -47,7 +47,7 @@ var dbCmd = &cli.Command{ Flags: []cli.Flag{ orgFlag(), &cli.StringFlag{Name: "query", Aliases: s1("u"), Usage: "Query string to execute using your connection."}, - &cli.BoolFlag{Name: "color", Aliases: s1("c"), Usage: "Display colors. Default true if tty.", Value: IsTty}, + colorFlag(), }, Action: cliAction(func(c *cli.Context, ac appcontext.AppContext, ctx context.Context) error { useColors := c.Bool("color") diff --git a/cmd/cmd_flags.go b/cmd/cmd_flags.go index 287af96..5a0341f 100644 --- a/cmd/cmd_flags.go +++ b/cmd/cmd_flags.go @@ -132,3 +132,7 @@ func stringPtrFlag(c *cli.Context, key string) *string { s := c.String(key) return &s } + +func colorFlag() *cli.BoolFlag { + return &cli.BoolFlag{Name: "color", Aliases: s1("c"), Usage: "Display colors. Default true if tty.", Value: IsTty} +} diff --git a/cmd/cmd_saved_queries.go b/cmd/cmd_saved_queries.go new file mode 100644 index 0000000..b5395f4 --- /dev/null +++ b/cmd/cmd_saved_queries.go @@ -0,0 +1,171 @@ +package cmd + +import ( + "context" + "github.com/urfave/cli/v2" + "github.com/webhookdb/webhookdb-cli/appcontext" + "github.com/webhookdb/webhookdb-cli/client" +) + +var savedQueriesCmd = &cli.Command{ + Name: "saved-query", + Aliases: []string{"saved-queries", "custom-query", "custom-queries"}, + Usage: "Manage your library of saved queries, including ones that can be accessed publicly.", + Subcommands: []*cli.Command{ + { + Name: "create", + Usage: "Create a new saved query.", + Flags: []cli.Flag{ + orgFlag(), + &cli.StringFlag{ + Name: "description", + Aliases: []string{"d", "desc"}, + Usage: "Explain what this query is used for.", + }, + &cli.StringFlag{ + Name: "sql", + Usage: "SQL statement to run.", + }, + &cli.BoolFlag{ + Name: "public", + Usage: "If true, the query can be accessed publicly, without authentication. " + + "Allows a saved query to be used on public dashboards or websites, " + + "without exposing a database connection string or " + + "allowing public access to a database.", + }, + }, + Action: cliAction(func(c *cli.Context, ac appcontext.AppContext, ctx context.Context) error { + input := client.SavedQueryCreateInput{ + OrgIdentifier: getOrgFlag(c, ac.Prefs), + Description: c.String("description"), + Sql: c.String("sql"), + Public: false, + } + out, err := client.SavedQueryCreate(ctx, ac.Auth, input) + if err != nil { + return err + } + printlnif(c, out.Message, false) + return nil + }), + }, + { + Name: "list", + Usage: "List all saved queries.", + Flags: []cli.Flag{ + orgFlag(), + formatFlag(), + }, + Action: cliAction(func(c *cli.Context, ac appcontext.AppContext, ctx context.Context) error { + input := client.SavedQueryListInput{ + OrgIdentifier: getOrgFlag(c, ac.Prefs), + } + out, err := client.SavedQueryList(ctx, ac.Auth, input) + if err != nil { + return err + } + printlnif(c, out.Message(), true) + return getFormatFlag(c).WriteCollection(c.App.Writer, out) + }), + }, + { + Name: "update", + Usage: "Update a new saved query.", + Aliases: []string{"edit", "modify"}, + Flags: []cli.Flag{ + orgFlag(), + savedQueryFlag(), + fieldFlag(), + valueFlag(), + }, + Action: cliAction(func(c *cli.Context, ac appcontext.AppContext, ctx context.Context) error { + input := client.SavedQueryUpdateInput{ + OrgIdentifier: getOrgFlag(c, ac.Prefs), + Identifier: getSavedQueryArgOrFlag(c), + Field: c.String("field"), + Value: c.String("value"), + } + out, err := client.SavedQueryUpdate(ctx, ac.Auth, input) + if err != nil { + return err + } + printlnif(c, out.Message, false) + return nil + }), + }, + { + Name: "info", + Usage: "Display information about given saved query.", + Flags: []cli.Flag{ + orgFlag(), + savedQueryFlag(), + &cli.StringFlag{ + Name: "field", + Aliases: s1("f"), + Usage: "The field that you want information about.", + }, + }, + Action: cliAction(func(c *cli.Context, ac appcontext.AppContext, ctx context.Context) error { + input := client.SavedQueryInfoInput{ + OrgIdentifier: getOrgFlag(c, ac.Prefs), + Identifier: getSavedQueryArgOrFlag(c), + Field: c.String("field"), + } + out, err := client.SavedQueryInfo(ctx, ac.Auth, input) + if err != nil { + return err + } + _, err = out.Blocks.WriteTo(c.App.Writer) + return err + }), + }, + { + Name: "run", + Usage: "Run the query.", + Flags: []cli.Flag{ + savedQueryFlag(), + colorFlag(), + }, + Action: cliAction(func(c *cli.Context, ac appcontext.AppContext, ctx context.Context) error { + useColors := c.Bool("color") + out, err := client.SavedQueryRun(ctx, ac.Auth, client.SavedQueryIdentifierInput{ + OrgIdentifier: getOrgFlag(c, ac.Prefs), + Identifier: getSavedQueryArgOrFlag(c), + }) + if err != nil { + return err + } + err = printSqlOutput(c, out, useColors) + return nil + }), + }, + { + Name: "delete", + Usage: "Delete this saved query.", + Flags: []cli.Flag{savedQueryFlag()}, + Action: cliAction(func(c *cli.Context, ac appcontext.AppContext, ctx context.Context) error { + out, err := client.SavedQueryDelete(ctx, ac.Auth, client.SavedQueryIdentifierInput{ + OrgIdentifier: getOrgFlag(c, ac.Prefs), + Identifier: getSavedQueryArgOrFlag(c), + }) + if err != nil { + return err + } + printlnif(c, out.Message, false) + return nil + }), + }, + }, +} + +func savedQueryFlag() *cli.StringFlag { + return &cli.StringFlag{ + Name: "saved-query", + Aliases: s1("q"), + Usage: usage("Saved query opaque id. Run `webhookdb saved-query list` to see a list of all your saved queries."), + } +} + +func getSavedQueryArgOrFlag(c *cli.Context) string { + return requireFlagOrArg(c, "saved-query", "Use `webhookdb saved-query list` to see available saved queries.") +} diff --git a/types/types.go b/types/types.go index 7f92d8f..c29d937 100644 --- a/types/types.go +++ b/types/types.go @@ -1,6 +1,7 @@ package types import ( + "encoding/json" "fmt" "strconv" ) @@ -117,3 +118,11 @@ func respDisplayHeaders(cr map[string]interface{}) DisplayHeaders { func SPtr(s string) *string { return &s } + +type RunQueryOutput struct { + // Use RawMessage to avoid deserializing the JSON right away. + // This allows us to render maps and certain other types verbatim. + Rows [][]json.RawMessage `json:"rows"` + Headers []string `json:"headers"` + MaxRowsReached bool `json:"max_rows_reached"` +}