Skip to content

Commit ae88c2c

Browse files
authored
feat: page size and pagination for get notifications cmd (#235)
* feat: page size and pagination for get notifications cmd * fix(notifications): get all by default * fix: use url.Values to build query params
1 parent 1f84651 commit ae88c2c

6 files changed

Lines changed: 178 additions & 8 deletions

File tree

api/client/base_client.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,17 @@ func (c *BaseClient) List(kind string) ([]byte, int, error) {
113113
}
114114

115115
func (c *BaseClient) ListWithParams(kind string, query url.Values) ([]byte, int, error) {
116+
if len(query) == 0 {
117+
return c.List(kind)
118+
}
116119
url := fmt.Sprintf("https://%s/api/%s/%s?%s", c.host, c.apiVersion, kind, query.Encode())
117120

118121
log.Printf("GET %s\n", url)
119122

120123
req, err := http.NewRequest("GET", url, nil)
121-
124+
if err != nil {
125+
return nil, 0, err
126+
}
122127
req.Header.Set("Content-Type", "application/json")
123128
req.Header.Set("Authorization", fmt.Sprintf("Token %s", c.authToken))
124129
req.Header.Set("User-Agent", UserAgent)

api/client/notifications_v1_alpha.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package client
33
import (
44
"errors"
55
"fmt"
6+
"net/url"
67

78
models "github.com/semaphoreci/cli/api/models"
89
)
@@ -24,8 +25,18 @@ func NewNotificationsV1AlphaApi() NotificationsV1AlphaApi {
2425
}
2526
}
2627

27-
func (c *NotificationsV1AlphaApi) ListNotifications() (*models.NotificationListV1Alpha, error) {
28-
body, status, err := c.BaseClient.List(c.ResourceNamePlural)
28+
func (c *NotificationsV1AlphaApi) ListNotifications(pageSize int32, pageToken string) (*models.NotificationListV1Alpha, error) {
29+
query := url.Values{}
30+
31+
if pageSize > 0 {
32+
query.Add("page_size", fmt.Sprintf("%d", pageSize))
33+
}
34+
35+
if pageToken != "" {
36+
query.Add("page_token", pageToken)
37+
}
38+
39+
body, status, err := c.BaseClient.ListWithParams(c.ResourceNamePlural, query)
2940

3041
if err != nil {
3142
return nil, fmt.Errorf("connecting to Semaphore failed '%s'", err)

api/models/notification_list_v1_alpha.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import (
55
)
66

77
type NotificationListV1Alpha struct {
8-
Notifications []NotificationV1Alpha `json:"notifications" yaml:"notifications"`
8+
Notifications []NotificationV1Alpha `json:"notifications" yaml:"notifications"`
9+
NextPageToken string `json:"next_page_token,omitempty" yaml:"next_page_token,omitempty"`
910
}
1011

1112
func NewNotificationListV1AlphaFromJson(data []byte) (*NotificationListV1Alpha, error) {

cmd/get.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,8 @@ func init() {
523523
RootCmd.AddCommand(getCmd)
524524

525525
getNotificationCmd := NewGetNotificationCmd()
526+
getNotificationCmd.Flags().Int32P("page-size", "s", 0, "Number of items per page (max 300)")
527+
getNotificationCmd.Flags().StringP("page-token", "t", "", "Token for the next page")
526528

527529
getCmd.AddCommand(GetDashboardCmd)
528530
getCmd.AddCommand(getNotificationCmd)

cmd/get_notification.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"text/tabwriter"
77

88
client "github.com/semaphoreci/cli/api/client"
9+
models "github.com/semaphoreci/cli/api/models"
910

1011
"github.com/semaphoreci/cli/cmd/utils"
1112
"github.com/spf13/cobra"
@@ -49,22 +50,39 @@ func RunGetNotification(cmd *cobra.Command, args []string) {
4950
func RunListNotifications(cmd *cobra.Command, args []string) {
5051
c := client.NewNotificationsV1AlphaApi()
5152

52-
notifList, err := c.ListNotifications()
53+
pageSize, _ := cmd.Flags().GetInt32("page-size")
54+
pageToken, _ := cmd.Flags().GetString("page-token")
55+
fetchAll := (pageSize == 0) && (pageToken == "")
5356

54-
utils.Check(err)
57+
var allNotifications []models.NotificationV1Alpha
58+
59+
for {
60+
notifList, err := c.ListNotifications(pageSize, pageToken)
61+
utils.Check(err)
62+
63+
allNotifications = append(allNotifications, notifList.Notifications...)
64+
pageToken = notifList.NextPageToken
65+
66+
if !fetchAll || notifList.NextPageToken == "" {
67+
break
68+
}
69+
}
5570

5671
const padding = 3
5772
w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, ' ', 0)
5873

5974
fmt.Fprintln(w, "NAME\tAGE")
6075

61-
for _, n := range notifList.Notifications {
76+
for _, n := range allNotifications {
6277
updateTime, err := n.Metadata.UpdateTime.Int64()
63-
6478
utils.Check(err)
6579

6680
fmt.Fprintf(w, "%s\t%s\n", n.Metadata.Name, utils.RelativeAgeForHumans(updateTime))
6781
}
82+
if !fetchAll && pageToken != "" {
83+
fmt.Fprintf(w, "\nNext page token: %s\n", pageToken)
84+
fmt.Fprintf(w, "To view next page, run: sem get notifications --page-token %s\n", pageToken)
85+
}
6886

6987
if err := w.Flush(); err != nil {
7088
fmt.Printf("Error flushing when pretty printing notifications: %v\n", err)

cmd/get_notification_test.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"testing"
7+
8+
httpmock "github.com/jarcoal/httpmock"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func Test__ListNotifications__Response200(t *testing.T) {
13+
httpmock.Activate()
14+
defer httpmock.DeactivateAndReset()
15+
16+
received := false
17+
18+
httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/notifications",
19+
func(req *http.Request) (*http.Response, error) {
20+
received = true
21+
22+
n1 := `{
23+
"metadata": {
24+
"name": "notif1",
25+
"update_time": "124"
26+
}
27+
}`
28+
29+
n2 := `{
30+
"metadata": {
31+
"name": "notif2",
32+
"update_time": "125"
33+
}
34+
}`
35+
36+
notifications := fmt.Sprintf(`{"notifications":[%s,%s]}`, n1, n2)
37+
38+
return httpmock.NewStringResponse(200, notifications), nil
39+
},
40+
)
41+
42+
RootCmd.SetArgs([]string{"get", "notifications"})
43+
RootCmd.Execute()
44+
45+
assert.True(t, received, "Expected the API to receive GET notifications")
46+
}
47+
48+
func Test__GetNotification__Response200(t *testing.T) {
49+
httpmock.Activate()
50+
defer httpmock.DeactivateAndReset()
51+
52+
received := false
53+
54+
httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/notifications/test-notif",
55+
func(req *http.Request) (*http.Response, error) {
56+
received = true
57+
58+
notification := `{
59+
"metadata": {
60+
"name": "test-notif",
61+
"update_time": "124"
62+
}
63+
}`
64+
65+
return httpmock.NewStringResponse(200, notification), nil
66+
},
67+
)
68+
69+
RootCmd.SetArgs([]string{"get", "notifications", "test-notif"})
70+
RootCmd.Execute()
71+
72+
assert.True(t, received, "Expected the API to receive GET notifications/test-notif")
73+
}
74+
75+
func Test__ListNotifications__WithAllFlag(t *testing.T) {
76+
httpmock.Activate()
77+
defer httpmock.DeactivateAndReset()
78+
79+
page1Called := false
80+
httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/notifications",
81+
func(req *http.Request) (*http.Response, error) {
82+
page1Called = true
83+
84+
n1 := `{
85+
"metadata": {
86+
"name": "notif1",
87+
"update_time": "124"
88+
}
89+
}`
90+
91+
n2 := `{
92+
"metadata": {
93+
"name": "notif2",
94+
"update_time": "125"
95+
}
96+
}`
97+
98+
response := fmt.Sprintf(`{
99+
"notifications": [%s,%s],
100+
"next_page_token": "page2"
101+
}`, n1, n2)
102+
103+
return httpmock.NewStringResponse(200, response), nil
104+
},
105+
)
106+
107+
page2Called := false
108+
httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/notifications?page_token=page2",
109+
func(req *http.Request) (*http.Response, error) {
110+
page2Called = true
111+
112+
n3 := `{
113+
"metadata": {
114+
"name": "notif3",
115+
"update_time": "126"
116+
}
117+
}`
118+
119+
response := fmt.Sprintf(`{
120+
"notifications": [%s],
121+
"next_page_token": ""
122+
}`, n3)
123+
124+
return httpmock.NewStringResponse(200, response), nil
125+
},
126+
)
127+
128+
RootCmd.SetArgs([]string{"get", "notifications"})
129+
RootCmd.Execute()
130+
131+
assert.True(t, page1Called, "Expected the API to receive GET notifications for first page")
132+
assert.True(t, page2Called, "Expected the API to receive GET notifications for second page")
133+
}

0 commit comments

Comments
 (0)