mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: Implement unified pagination and add template versions support (#1308)
* feat: Implement pagination for template versions * feat: Use unified pagination between users and template versions * Sync codepaths between users and template versions * Create requestOption type in codersdk and add test * Fix created_at edge case for pagination cursor in queries * feat: Add support for json omitempty and embedded structs in apitypings (#1318) * Add scripts/apitypings/main.go to Makefile
This commit is contained in:
+3
-1
@@ -33,9 +33,11 @@ type Client struct {
|
||||
URL *url.URL
|
||||
}
|
||||
|
||||
type requestOption func(*http.Request)
|
||||
|
||||
// request performs an HTTP request with the body provided.
|
||||
// The caller is responsible for closing the response body.
|
||||
func (c *Client) request(ctx context.Context, method, path string, body interface{}, opts ...func(r *http.Request)) (*http.Response, error) {
|
||||
func (c *Client) request(ctx context.Context, method, path string, body interface{}, opts ...requestOption) (*http.Response, error) {
|
||||
serverURL, err := c.URL.Parse(path)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse url: %w", err)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package codersdk
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Pagination sets pagination options for the endpoints that support it.
|
||||
type Pagination struct {
|
||||
// AfterID returns all or up to Limit results after the given
|
||||
// UUID. This option can be used with or as an alternative to
|
||||
// Offset for better performance. To use it as an alternative,
|
||||
// set AfterID to the last UUID returned by the previous
|
||||
// request.
|
||||
AfterID uuid.UUID `json:"after_id,omitempty"`
|
||||
// Limit sets the maximum number of users to be returned
|
||||
// in a single page. If the limit is <= 0, there is no limit
|
||||
// and all users are returned.
|
||||
Limit int `json:"limit,omitempty"`
|
||||
// Offset is used to indicate which page to return. An offset of 0
|
||||
// returns the first 'limit' number of users.
|
||||
// To get the next page, use offset=<limit>*<page_number>.
|
||||
// Offset is 0 indexed, so the first record sits at offset 0.
|
||||
Offset int `json:"offset,omitempty"`
|
||||
}
|
||||
|
||||
// asRequestOption returns a function that can be used in (*Client).request.
|
||||
// It modifies the request query parameters.
|
||||
func (p Pagination) asRequestOption() requestOption {
|
||||
return func(r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
if p.AfterID != uuid.Nil {
|
||||
q.Set("after_id", p.AfterID.String())
|
||||
}
|
||||
if p.Limit > 0 {
|
||||
q.Set("limit", strconv.Itoa(p.Limit))
|
||||
}
|
||||
if p.Offset > 0 {
|
||||
q.Set("offset", strconv.Itoa(p.Offset))
|
||||
}
|
||||
r.URL.RawQuery = q.Encode()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//nolint:testpackage
|
||||
package codersdk
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPagination_asRequestOption(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
uuid1 := uuid.New()
|
||||
type fields struct {
|
||||
AfterID uuid.UUID
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want url.Values
|
||||
}{
|
||||
{
|
||||
name: "Test AfterID is set",
|
||||
fields: fields{AfterID: uuid1},
|
||||
want: url.Values{"after_id": []string{uuid1.String()}},
|
||||
},
|
||||
{
|
||||
name: "Test Limit is set",
|
||||
fields: fields{Limit: 10},
|
||||
want: url.Values{"limit": []string{"10"}},
|
||||
},
|
||||
{
|
||||
name: "Test Offset is set",
|
||||
fields: fields{Offset: 10},
|
||||
want: url.Values{"offset": []string{"10"}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := Pagination{
|
||||
AfterID: tt.fields.AfterID,
|
||||
Limit: tt.fields.Limit,
|
||||
Offset: tt.fields.Offset,
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
p.asRequestOption()(req)
|
||||
got := req.URL.Query()
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -69,9 +69,16 @@ func (c *Client) UpdateActiveTemplateVersion(ctx context.Context, template uuid.
|
||||
return nil
|
||||
}
|
||||
|
||||
// TemplateVersionsByTemplateRequest defines the request parameters for
|
||||
// TemplateVersionsByTemplate.
|
||||
type TemplateVersionsByTemplateRequest struct {
|
||||
TemplateID uuid.UUID `json:"template_id" validate:"required"`
|
||||
Pagination
|
||||
}
|
||||
|
||||
// TemplateVersionsByTemplate lists versions associated with a template.
|
||||
func (c *Client) TemplateVersionsByTemplate(ctx context.Context, template uuid.UUID) ([]TemplateVersion, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templates/%s/versions", template), nil)
|
||||
func (c *Client) TemplateVersionsByTemplate(ctx context.Context, req TemplateVersionsByTemplateRequest) ([]TemplateVersion, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templates/%s/versions", req.TemplateID), nil, req.Pagination.asRequestOption())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+11
-25
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -22,19 +21,10 @@ const (
|
||||
)
|
||||
|
||||
type UsersRequest struct {
|
||||
AfterUser uuid.UUID `json:"after_user"`
|
||||
Search string `json:"search"`
|
||||
// Limit sets the maximum number of users to be returned
|
||||
// in a single page. If the limit is <= 0, there is no limit
|
||||
// and all users are returned.
|
||||
Limit int `json:"limit"`
|
||||
// Offset is used to indicate which page to return. An offset of 0
|
||||
// returns the first 'limit' number of users.
|
||||
// To get the next page, use offset=<limit>*<page_number>.
|
||||
// Offset is 0 indexed, so the first record sits at offset 0.
|
||||
Offset int `json:"offset"`
|
||||
Search string `json:"search"`
|
||||
// Filter users by status
|
||||
Status string `json:"status"`
|
||||
Pagination
|
||||
}
|
||||
|
||||
// User represents a user in Coder.
|
||||
@@ -317,19 +307,15 @@ func (c *Client) userByIdentifier(ctx context.Context, ident string) (User, erro
|
||||
// Users returns all users according to the request parameters. If no parameters are set,
|
||||
// the default behavior is to return all users in a single page.
|
||||
func (c *Client) Users(ctx context.Context, req UsersRequest) ([]User, error) {
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users"), nil, func(r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
if req.AfterUser != uuid.Nil {
|
||||
q.Set("after_user", req.AfterUser.String())
|
||||
}
|
||||
if req.Limit > 0 {
|
||||
q.Set("limit", strconv.Itoa(req.Limit))
|
||||
}
|
||||
q.Set("offset", strconv.Itoa(req.Offset))
|
||||
q.Set("search", req.Search)
|
||||
q.Set("status", req.Status)
|
||||
r.URL.RawQuery = q.Encode()
|
||||
})
|
||||
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users"), nil,
|
||||
req.Pagination.asRequestOption(),
|
||||
func(r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
q.Set("search", req.Search)
|
||||
q.Set("status", req.Status)
|
||||
r.URL.RawQuery = q.Encode()
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return []User{}, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user