Skip to content

Commit 3adb3fc

Browse files
authored
Update the query command to have the -i flag (#31)
Also clean up the query command so it works without any inputs provided. In such cases it uses sensible defaults.
1 parent 39054ff commit 3adb3fc

File tree

16 files changed

+167
-104
lines changed

16 files changed

+167
-104
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
## pb
22

3-
pb is the command line interface for [Parseable Server](https://github.com/parseablehq/parseable). pb allows you to manage Streams, Users, and Data on Parseable Server. You can use pb to manage multiple Parseable Server instances using Profiles.
3+
Dashboard fatigue is one of key reasons for poor adoption of logging tools among developers. With pb, we intend to bring the familiar command line interface for querying and analyzing log data at scale.
44

5-
We believe dashboard fatigue is one of key reasons for poor adoption of logging tools among developers. With pb, we intend to bring the familiar command line interface for querying and analyzing log data at scale.
5+
pb is the command line interface for [Parseable Server](https://github.com/parseablehq/parseable). pb allows you to manage Streams, Users, and Data on Parseable Server. You can use pb to manage multiple Parseable Server instances using Profiles.
66

77
![pb banner](https://github.com/parseablehq/.github/blob/main/images/pb/pb.png?raw=true)
88

@@ -22,7 +22,7 @@ chmod +x pb && mv pb /usr/local/bin
2222
pb comes configured with `demo` profile as the default. This means you can directly start using pb against the [demo Parseable Server](https://demo.parseable.io). For example, to query the stream `backend` on demo server, run:
2323

2424
```bash
25-
pb query backend
25+
pb query backend
2626
```
2727

2828
#### Profiles

cmd/client.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ import (
2020
"io"
2121
"net/http"
2222
"net/url"
23-
"pb/pkg/config"
2423
"time"
24+
25+
"pb/pkg/config"
2526
)
2627

2728
type HTTPClient struct {

cmd/pre.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package cmd
1919
import (
2020
"errors"
2121
"os"
22+
2223
"pb/pkg/config"
2324

2425
"github.com/spf13/cobra"

cmd/profile.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"fmt"
2121
"net/url"
2222
"os"
23+
2324
"pb/pkg/config"
2425
"pb/pkg/model/credential"
2526
"pb/pkg/model/defaultprofile"

cmd/query.go

Lines changed: 112 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,93 +17,99 @@ package cmd
1717

1818
import (
1919
"bytes"
20+
"encoding/json"
21+
"errors"
2022
"fmt"
2123
"io"
2224
"os"
25+
"time"
26+
2327
"pb/pkg/model"
24-
"strconv"
2528

2629
tea "github.com/charmbracelet/bubbletea"
2730
"github.com/spf13/cobra"
2831
)
2932

3033
var (
31-
durationFlag = "duration"
32-
durationFlagShort = "d"
33-
defaultDuration = "10"
34-
3534
startFlag = "from"
3635
startFlagShort = "f"
3736
defaultStart = "1m"
3837

3938
endFlag = "to"
4039
endFlagShort = "t"
4140
defaultEnd = "now"
41+
42+
interactiveFlag = "interactive"
43+
interactiveFlagShort = "i"
4244
)
4345

44-
var queryInteractive = &cobra.Command{
45-
Use: "i [stream-name] --duration 10",
46-
Example: " pb query frontend --duration 10",
47-
Short: "Interactive query table view",
48-
Long: "\n command is used to open a prompt to query a stream.",
49-
Args: cobra.ExactArgs(1),
46+
var query = &cobra.Command{
47+
Use: "query [query] [flags]",
48+
Example: " pb query \"select * from frontend\" --from=10m --to=now",
49+
Short: "Run SQL query on a log stream",
50+
Long: "\nqRun SQL query on a log stream. Default output format is json. Use -i flag to open interactive table view.",
51+
Args: cobra.MaximumNArgs(1),
5052
PreRunE: PreRunDefaultProfile,
5153
RunE: func(command *cobra.Command, args []string) error {
52-
stream := args[0]
53-
duration, _ := command.Flags().GetString(durationFlag)
54-
55-
if duration == "" {
56-
duration = defaultDuration
54+
var query string
55+
56+
// if no query is provided set it to default "select * from <steam-name>"
57+
// <steam-name> here is the first stream that server returns
58+
if len(args) == 0 || args[0] == "" || args[0] == " " {
59+
stream, err := fetchFirstStream()
60+
if err != nil {
61+
return err
62+
}
63+
query = fmt.Sprintf("select * from %s", stream)
64+
} else {
65+
query = args[0]
5766
}
58-
durationInt, err := strconv.Atoi(duration)
67+
68+
start, err := command.Flags().GetString(startFlag)
5969
if err != nil {
6070
return err
6171
}
62-
63-
p := tea.NewProgram(model.NewQueryModel(DefaultProfile, stream, uint(durationInt)), tea.WithAltScreen())
64-
if _, err := p.Run(); err != nil {
65-
fmt.Printf("there's been an error: %v", err)
66-
os.Exit(1)
72+
if start == "" {
73+
start = defaultStart
6774
}
6875

69-
return nil
70-
},
71-
}
72-
73-
var queryJSON = &cobra.Command{
74-
Use: "query [query] --from=10m --to=now",
75-
Example: " pb query \"select * from frontend\" --from=10m --to=now",
76-
Short: "Run SQL query",
77-
Long: "\nquery command is used to run query. Output format is json string",
78-
Args: cobra.ExactArgs(1),
79-
PreRunE: PreRunDefaultProfile,
80-
RunE: func(command *cobra.Command, args []string) error {
81-
query := args[0]
82-
start, _ := command.Flags().GetString(startFlag)
8376
end, _ := command.Flags().GetString(endFlag)
84-
85-
if start == "" {
86-
start = defaultStart
77+
if err != nil {
78+
return err
8779
}
8880
if end == "" {
8981
end = defaultEnd
9082
}
9183

84+
interactive, _ := command.Flags().GetBool(interactiveFlag)
85+
if err != nil {
86+
return err
87+
}
88+
89+
startTime, endTime, err := parseTime(start, end)
90+
if err != nil {
91+
return err
92+
}
93+
94+
if interactive {
95+
p := tea.NewProgram(model.NewQueryModel(DefaultProfile, query, startTime, endTime), tea.WithAltScreen())
96+
if _, err := p.Run(); err != nil {
97+
fmt.Printf("there's been an error: %v", err)
98+
os.Exit(1)
99+
}
100+
return nil
101+
}
102+
92103
client := DefaultClient()
93104
return fetchData(&client, query, start, end)
94105
},
95106
}
96107

97-
var QueryInteractiveCmd = func() *cobra.Command {
98-
queryInteractive.Flags().StringP(durationFlag, durationFlagShort, defaultDuration, "specify the duration in minutes for which queries should be executed. Defaults to 10 minutes")
99-
return queryInteractive
100-
}()
101-
102108
var QueryCmd = func() *cobra.Command {
103-
queryJSON.Flags().StringP(startFlag, startFlagShort, defaultStart, "Specify start datetime of query. Supports RFC3999 time format and durations (ex. 10m, 1hr ..) ")
104-
queryJSON.Flags().StringP(endFlag, endFlagShort, defaultEnd, "Specify end datetime of query. Supports RFC3999 time format and literal - now ")
105-
queryJSON.AddCommand(queryInteractive)
106-
return queryJSON
109+
query.Flags().BoolP(interactiveFlag, interactiveFlagShort, false, "open the query result in interactive mode")
110+
query.Flags().StringP(startFlag, startFlagShort, defaultStart, "Start time for query. Takes date as '2023-10-12T07:20:50.52Z' or string like '10m', '1hr'")
111+
query.Flags().StringP(endFlag, endFlagShort, defaultEnd, "End time for query. Takes date as '2023-10-12T07:20:50.52Z' or 'now'")
112+
return query
107113
}()
108114

109115
func fetchData(client *HTTPClient, query string, startTime string, endTime string) (err error) {
@@ -134,3 +140,61 @@ func fetchData(client *HTTPClient, query string, startTime string, endTime strin
134140
}
135141
return
136142
}
143+
144+
func fetchFirstStream() (string, error) {
145+
client := DefaultClient()
146+
req, err := client.NewRequest("GET", "logstream", nil)
147+
if err != nil {
148+
return "", err
149+
}
150+
151+
resp, err := client.client.Do(req)
152+
if err != nil {
153+
return "", err
154+
}
155+
156+
if resp.StatusCode == 200 {
157+
items := []map[string]string{}
158+
if err := json.NewDecoder(resp.Body).Decode(&items); err != nil {
159+
return "", err
160+
}
161+
defer resp.Body.Close()
162+
163+
if len(items) == 0 {
164+
return "", errors.New("no stream found on the server, please create a stream to proceed")
165+
}
166+
// return with the first stream that is present in the list
167+
for _, v := range items {
168+
return v["name"], nil
169+
}
170+
}
171+
return "", fmt.Errorf("received error status code %d from server", resp.StatusCode)
172+
}
173+
174+
// Returns start and end time for query in RFC3339 format
175+
func parseTime(start, end string) (time.Time, time.Time, error) {
176+
if start == defaultStart && end == defaultEnd {
177+
return time.Now().Add(-1 * time.Minute), time.Now(), nil
178+
}
179+
180+
startTime, err := time.Parse(time.RFC3339, start)
181+
if err != nil {
182+
// try parsing as duration
183+
duration, err := time.ParseDuration(start)
184+
if err != nil {
185+
return time.Time{}, time.Time{}, err
186+
}
187+
startTime = time.Now().Add(-1 * duration)
188+
}
189+
190+
endTime, err := time.Parse(time.RFC3339, end)
191+
if err != nil {
192+
if end == "now" {
193+
endTime = time.Now()
194+
} else {
195+
return time.Time{}, time.Time{}, err
196+
}
197+
}
198+
199+
return startTime, endTime, nil
200+
}

cmd/role.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ import (
2121
"fmt"
2222
"io"
2323
"os"
24-
"pb/pkg/model/role"
2524
"strings"
2625
"sync"
2726

27+
"pb/pkg/model/role"
28+
2829
tea "github.com/charmbracelet/bubbletea"
2930
"github.com/charmbracelet/lipgloss"
3031
"github.com/spf13/cobra"

cmd/stream.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ var StatStreamCmd = &cobra.Command{
150150
return err
151151
}
152152

153-
isRententionSet := len(retention) > 0
153+
isRetentionSet := len(retention) > 0
154154

155155
fmt.Println(styleBold.Render("\nInfo:"))
156156
fmt.Printf(" Event Count: %d\n", ingestionCount)
@@ -161,7 +161,7 @@ var StatStreamCmd = &cobra.Command{
161161
100-(float64(storageSize)/float64(ingestionSize))*100, "%")
162162
fmt.Println()
163163

164-
if isRententionSet {
164+
if isRetentionSet {
165165
fmt.Println(styleBold.Render("Retention:"))
166166
for _, item := range retention {
167167
fmt.Printf(" Action: %s\n", styleBold.Render(item.Action))

cmd/tail.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"encoding/base64"
2323
"encoding/json"
2424
"fmt"
25+
2526
"pb/pkg/config"
2627

2728
"github.com/apache/arrow/go/v13/arrow/array"

cmd/user.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import (
2121
"fmt"
2222
"io"
2323
"os"
24-
"pb/pkg/model/role"
2524
"sync"
2625

26+
"pb/pkg/model/role"
27+
2728
tea "github.com/charmbracelet/bubbletea"
2829
"github.com/charmbracelet/lipgloss"
2930
"github.com/spf13/cobra"

main.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package main
1919
import (
2020
"errors"
2121
"os"
22+
2223
"pb/cmd"
2324
"pb/pkg/config"
2425

@@ -60,8 +61,8 @@ var cli = &cobra.Command{
6061

6162
var profile = &cobra.Command{
6263
Use: "profile",
63-
Short: "Manage profiles",
64-
Long: "\nuse profile command to configure (multiple) Parseable instances. Each profile takes a URL and credentials.",
64+
Short: "Manage different Parseable targets",
65+
Long: "\nuse profile command to configure different Parseable instances. Each profile takes a URL and credentials.",
6566
}
6667

6768
var user = &cobra.Command{

0 commit comments

Comments
 (0)