Skip to content

Commit

Permalink
Support for saved queries
Browse files Browse the repository at this point in the history
  • Loading branch information
rgalanakis committed Feb 5, 2024
1 parent f29f6aa commit 52210b1
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 16 deletions.
9 changes: 1 addition & 8 deletions client/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package client

import (
"context"
"encoding/json"
"github.com/webhookdb/webhookdb-cli/types"
)

Expand Down Expand Up @@ -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)
Expand Down
7 changes: 0 additions & 7 deletions client/entities.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
70 changes: 70 additions & 0 deletions client/saved_queries.go
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Documentation: <https://docs.webhookdb.com/docs/cli-reference.md>
integrationsCmd,
organizationsCmd,
replayCmd,
savedQueriesCmd,
servicesCmd,
subscriptionsCmd,
syncCmd(dbSyncType),
Expand Down
2 changes: 1 addition & 1 deletion cmd/cmd_db.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 4 additions & 0 deletions cmd/cmd_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}
171 changes: 171 additions & 0 deletions cmd/cmd_saved_queries.go
Original file line number Diff line number Diff line change
@@ -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.")
}
9 changes: 9 additions & 0 deletions types/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types

import (
"encoding/json"
"fmt"
"strconv"
)
Expand Down Expand Up @@ -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"`
}

0 comments on commit 52210b1

Please sign in to comment.