mirror of
https://github.com/interviewstreet/go-jira.git
synced 2025-04-23 12:08:51 +02:00
Merge pull request #18 from nebril/paging
Wrap http.Response in Response struct to provide more information about paging
This commit is contained in:
commit
de4984d92a
40
issue.go
40
issue.go
@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -278,10 +277,18 @@ type CommentVisibility struct {
|
|||||||
Value string `json:"value,omitempty"`
|
Value string `json:"value,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SearchOptions struct {
|
||||||
|
StartAt int
|
||||||
|
MaxResults int
|
||||||
|
}
|
||||||
|
|
||||||
// searchResult is only a small wrapper arround the Search (with JQL) method
|
// searchResult is only a small wrapper arround the Search (with JQL) method
|
||||||
// to be able to parse the results
|
// to be able to parse the results
|
||||||
type searchResult struct {
|
type searchResult struct {
|
||||||
Issues []Issue `json:"issues"`
|
Issues []Issue `json:"issues"`
|
||||||
|
StartAt int `json:"startAt"`
|
||||||
|
MaxResults int `json:"maxResults"`
|
||||||
|
Total int `json:"total"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomFields represents custom fields of JIRA
|
// CustomFields represents custom fields of JIRA
|
||||||
@ -294,7 +301,7 @@ type CustomFields map[string]string
|
|||||||
// If the issue cannot be found via an exact match, JIRA will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved.
|
// If the issue cannot be found via an exact match, JIRA will also look for the issue in a case-insensitive way, or by looking to see if the issue was moved.
|
||||||
//
|
//
|
||||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getIssue
|
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getIssue
|
||||||
func (s *IssueService) Get(issueID string) (*Issue, *http.Response, error) {
|
func (s *IssueService) Get(issueID string) (*Issue, *Response, error) {
|
||||||
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID)
|
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID)
|
||||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -310,11 +317,11 @@ func (s *IssueService) Get(issueID string) (*Issue, *http.Response, error) {
|
|||||||
return issue, resp, nil
|
return issue, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DownloadAttachment returns a http.Response of an attachment for a given attachmentID.
|
// DownloadAttachment returns a Response of an attachment for a given attachmentID.
|
||||||
// The attachment is in the http.Response.Body of the response.
|
// The attachment is in the Response.Body of the response.
|
||||||
// This is an io.ReadCloser.
|
// This is an io.ReadCloser.
|
||||||
// The caller should close the resp.Body.
|
// The caller should close the resp.Body.
|
||||||
func (s *IssueService) DownloadAttachment(attachmentID string) (*http.Response, error) {
|
func (s *IssueService) DownloadAttachment(attachmentID string) (*Response, error) {
|
||||||
apiEndpoint := fmt.Sprintf("secure/attachment/%s/", attachmentID)
|
apiEndpoint := fmt.Sprintf("secure/attachment/%s/", attachmentID)
|
||||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -330,7 +337,7 @@ func (s *IssueService) DownloadAttachment(attachmentID string) (*http.Response,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PostAttachment uploads r (io.Reader) as an attachment to a given attachmentID
|
// PostAttachment uploads r (io.Reader) as an attachment to a given attachmentID
|
||||||
func (s *IssueService) PostAttachment(attachmentID string, r io.Reader, attachmentName string) (*[]Attachment, *http.Response, error) {
|
func (s *IssueService) PostAttachment(attachmentID string, r io.Reader, attachmentName string) (*[]Attachment, *Response, error) {
|
||||||
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/attachments", attachmentID)
|
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/attachments", attachmentID)
|
||||||
|
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
@ -371,7 +378,7 @@ func (s *IssueService) PostAttachment(attachmentID string, r io.Reader, attachme
|
|||||||
// The issueType field must correspond to a sub-task issue type and you must provide a parent field in the issue create request containing the id or key of the parent issue.
|
// The issueType field must correspond to a sub-task issue type and you must provide a parent field in the issue create request containing the id or key of the parent issue.
|
||||||
//
|
//
|
||||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-createIssues
|
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-createIssues
|
||||||
func (s *IssueService) Create(issue *Issue) (*Issue, *http.Response, error) {
|
func (s *IssueService) Create(issue *Issue) (*Issue, *Response, error) {
|
||||||
apiEndpoint := "rest/api/2/issue/"
|
apiEndpoint := "rest/api/2/issue/"
|
||||||
req, err := s.client.NewRequest("POST", apiEndpoint, issue)
|
req, err := s.client.NewRequest("POST", apiEndpoint, issue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -390,7 +397,7 @@ func (s *IssueService) Create(issue *Issue) (*Issue, *http.Response, error) {
|
|||||||
// AddComment adds a new comment to issueID.
|
// AddComment adds a new comment to issueID.
|
||||||
//
|
//
|
||||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-addComment
|
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-addComment
|
||||||
func (s *IssueService) AddComment(issueID string, comment *Comment) (*Comment, *http.Response, error) {
|
func (s *IssueService) AddComment(issueID string, comment *Comment) (*Comment, *Response, error) {
|
||||||
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment", issueID)
|
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment", issueID)
|
||||||
req, err := s.client.NewRequest("POST", apiEndpoint, comment)
|
req, err := s.client.NewRequest("POST", apiEndpoint, comment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -409,7 +416,7 @@ func (s *IssueService) AddComment(issueID string, comment *Comment) (*Comment, *
|
|||||||
// AddLink adds a link between two issues.
|
// AddLink adds a link between two issues.
|
||||||
//
|
//
|
||||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issueLink
|
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issueLink
|
||||||
func (s *IssueService) AddLink(issueLink *IssueLink) (*http.Response, error) {
|
func (s *IssueService) AddLink(issueLink *IssueLink) (*Response, error) {
|
||||||
apiEndpoint := fmt.Sprintf("rest/api/2/issueLink")
|
apiEndpoint := fmt.Sprintf("rest/api/2/issueLink")
|
||||||
req, err := s.client.NewRequest("POST", apiEndpoint, issueLink)
|
req, err := s.client.NewRequest("POST", apiEndpoint, issueLink)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -423,8 +430,15 @@ func (s *IssueService) AddLink(issueLink *IssueLink) (*http.Response, error) {
|
|||||||
// Search will search for tickets according to the jql
|
// Search will search for tickets according to the jql
|
||||||
//
|
//
|
||||||
// JIRA API docs: https://developer.atlassian.com/jiradev/jira-apis/jira-rest-apis/jira-rest-api-tutorials/jira-rest-api-example-query-issues
|
// JIRA API docs: https://developer.atlassian.com/jiradev/jira-apis/jira-rest-apis/jira-rest-api-tutorials/jira-rest-api-example-query-issues
|
||||||
func (s *IssueService) Search(jql string) ([]Issue, *http.Response, error) {
|
func (s *IssueService) Search(jql string, options *SearchOptions) ([]Issue, *Response, error) {
|
||||||
u := fmt.Sprintf("rest/api/2/search?jql=%s", url.QueryEscape(jql))
|
var u string
|
||||||
|
if options == nil {
|
||||||
|
u = fmt.Sprintf("rest/api/2/search?jql=%s", url.QueryEscape(jql))
|
||||||
|
} else {
|
||||||
|
u = fmt.Sprintf("rest/api/2/search?jql=%s&startAt=%d&maxResults=%d", url.QueryEscape(jql),
|
||||||
|
options.StartAt, options.MaxResults)
|
||||||
|
}
|
||||||
|
|
||||||
req, err := s.client.NewRequest("GET", u, nil)
|
req, err := s.client.NewRequest("GET", u, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []Issue{}, nil, err
|
return []Issue{}, nil, err
|
||||||
@ -436,7 +450,7 @@ func (s *IssueService) Search(jql string) ([]Issue, *http.Response, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetCustomFields returns a map of customfield_* keys with string values
|
// GetCustomFields returns a map of customfield_* keys with string values
|
||||||
func (s *IssueService) GetCustomFields(issueID string) (CustomFields, *http.Response, error) {
|
func (s *IssueService) GetCustomFields(issueID string) (CustomFields, *Response, error) {
|
||||||
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID)
|
apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s", issueID)
|
||||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -316,11 +316,13 @@ func TestIssue_Search(t *testing.T) {
|
|||||||
defer teardown()
|
defer teardown()
|
||||||
testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) {
|
testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) {
|
||||||
testMethod(t, r, "GET")
|
testMethod(t, r, "GET")
|
||||||
testRequestURL(t, r, "/rest/api/2/search?jql=something")
|
testRequestURL(t, r, "/rest/api/2/search?jql=something&startAt=1&maxResults=40")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
fmt.Fprint(w, `{"expand": "schema,names","startAt": 0,"maxResults": 50,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}},{"expand": "html","id": "10004","self": "http://kelpie9:8081/rest/api/2/issue/BULK-47","key": "BULK-47","fields": {"summary": "Cheese v1 2.0 issue","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/3","id": "3","description": "A task that needs to be done.","iconUrl": "http://kelpie9:8081/images/icons/task.gif","name": "Task","subtask": false}}}]}`)
|
fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 40,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}},{"expand": "html","id": "10004","self": "http://kelpie9:8081/rest/api/2/issue/BULK-47","key": "BULK-47","fields": {"summary": "Cheese v1 2.0 issue","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/3","id": "3","description": "A task that needs to be done.","iconUrl": "http://kelpie9:8081/images/icons/task.gif","name": "Task","subtask": false}}}]}`)
|
||||||
})
|
})
|
||||||
_, resp, err := testClient.Issue.Search("something")
|
|
||||||
|
opt := &SearchOptions{StartAt: 1, MaxResults: 40}
|
||||||
|
_, resp, err := testClient.Issue.Search("something", opt)
|
||||||
|
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
t.Errorf("Response given: %+v", resp)
|
t.Errorf("Response given: %+v", resp)
|
||||||
@ -328,6 +330,47 @@ func TestIssue_Search(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error given: %s", err)
|
t.Errorf("Error given: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if resp.StartAt != 1 {
|
||||||
|
t.Errorf("StartAt should populate with 1, %v given", resp.StartAt)
|
||||||
|
}
|
||||||
|
if resp.MaxResults != 40 {
|
||||||
|
t.Errorf("StartAt should populate with 40, %v given", resp.MaxResults)
|
||||||
|
}
|
||||||
|
if resp.Total != 6 {
|
||||||
|
t.Errorf("StartAt should populate with 6, %v given", resp.Total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue_SearchWithoutPaging(t *testing.T) {
|
||||||
|
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "GET")
|
||||||
|
testRequestURL(t, r, "/rest/api/2/search?jql=something")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprint(w, `{"expand": "schema,names","startAt": 0,"maxResults": 50,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}},{"expand": "html","id": "10004","self": "http://kelpie9:8081/rest/api/2/issue/BULK-47","key": "BULK-47","fields": {"summary": "Cheese v1 2.0 issue","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/3","id": "3","description": "A task that needs to be done.","iconUrl": "http://kelpie9:8081/images/icons/task.gif","name": "Task","subtask": false}}}]}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, resp, err := testClient.Issue.Search("something", nil)
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
t.Errorf("Response given: %+v", resp)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error given: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StartAt != 0 {
|
||||||
|
t.Errorf("StartAt should populate with 0, %v given", resp.StartAt)
|
||||||
|
}
|
||||||
|
if resp.MaxResults != 50 {
|
||||||
|
t.Errorf("StartAt should populate with 50, %v given", resp.MaxResults)
|
||||||
|
}
|
||||||
|
if resp.Total != 6 {
|
||||||
|
t.Errorf("StartAt should populate with 6, %v given", resp.Total)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_CustomFields(t *testing.T) {
|
func Test_CustomFields(t *testing.T) {
|
||||||
|
41
jira.go
41
jira.go
@ -122,25 +122,26 @@ func (c *Client) NewMultiPartRequest(method, urlStr string, buf *bytes.Buffer) (
|
|||||||
|
|
||||||
// Do sends an API request and returns the API response.
|
// Do sends an API request and returns the API response.
|
||||||
// The API response is JSON decoded and stored in the value pointed to by v, or returned as an error if an API error has occurred.
|
// The API response is JSON decoded and stored in the value pointed to by v, or returned as an error if an API error has occurred.
|
||||||
func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
|
func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
|
||||||
resp, err := c.client.Do(req)
|
httpResp, err := c.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = CheckResponse(resp)
|
err = CheckResponse(httpResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Even though there was an error, we still return the response
|
// Even though there was an error, we still return the response
|
||||||
// in case the caller wants to inspect it further
|
// in case the caller wants to inspect it further
|
||||||
return resp, err
|
return newResponse(httpResp, nil), err
|
||||||
}
|
}
|
||||||
|
|
||||||
if v != nil {
|
if v != nil {
|
||||||
// Open a NewDecoder and defer closing the reader only if there is a provided interface to decode to
|
// Open a NewDecoder and defer closing the reader only if there is a provided interface to decode to
|
||||||
defer resp.Body.Close()
|
defer httpResp.Body.Close()
|
||||||
err = json.NewDecoder(resp.Body).Decode(v)
|
err = json.NewDecoder(httpResp.Body).Decode(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resp := newResponse(httpResp, v)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,3 +163,31 @@ func CheckResponse(r *http.Response) error {
|
|||||||
func (c *Client) GetBaseURL() url.URL {
|
func (c *Client) GetBaseURL() url.URL {
|
||||||
return *c.baseURL
|
return *c.baseURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Response represents JIRA API response. It wraps http.Response returned from
|
||||||
|
// API and provides information about paging.
|
||||||
|
type Response struct {
|
||||||
|
*http.Response
|
||||||
|
|
||||||
|
StartAt int
|
||||||
|
MaxResults int
|
||||||
|
Total int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newResponse(r *http.Response, v interface{}) *Response {
|
||||||
|
resp := &Response{Response: r}
|
||||||
|
resp.populatePageValues(v)
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets paging values if response json was parsed to searchResult type
|
||||||
|
// (can be extended with other types if they also need paging info)
|
||||||
|
func (r *Response) populatePageValues(v interface{}) {
|
||||||
|
switch value := v.(type) {
|
||||||
|
case *searchResult:
|
||||||
|
r.StartAt = value.StartAt
|
||||||
|
r.MaxResults = value.MaxResults
|
||||||
|
r.Total = value.Total
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
21
jira_test.go
21
jira_test.go
@ -296,3 +296,24 @@ func TestGetBaseURL_WithURL(t *testing.T) {
|
|||||||
t.Errorf("Base URLs are not equal. Expected %+v, got %+v", *u, b)
|
t.Errorf("Base URLs are not equal. Expected %+v, got %+v", *u, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPagingInfoEmptyByDefault(t *testing.T) {
|
||||||
|
c, _ := NewClient(nil, testJIRAInstanceURL)
|
||||||
|
req, _ := c.NewRequest("GET", "/", nil)
|
||||||
|
type foo struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
body := new(foo)
|
||||||
|
|
||||||
|
resp, _ := c.Do(req, body)
|
||||||
|
|
||||||
|
if resp.StartAt != 0 {
|
||||||
|
t.Errorf("StartAt not equal to 0")
|
||||||
|
}
|
||||||
|
if resp.MaxResults != 0 {
|
||||||
|
t.Errorf("StartAt not equal to 0")
|
||||||
|
}
|
||||||
|
if resp.Total != 0 {
|
||||||
|
t.Errorf("StartAt not equal to 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,7 +2,6 @@ package jira
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProjectService handles projects for the JIRA instance / API.
|
// ProjectService handles projects for the JIRA instance / API.
|
||||||
@ -73,7 +72,7 @@ type ProjectComponent struct {
|
|||||||
// GetList gets all projects form JIRA
|
// GetList gets all projects form JIRA
|
||||||
//
|
//
|
||||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects
|
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects
|
||||||
func (s *ProjectService) GetList() (*ProjectList, *http.Response, error) {
|
func (s *ProjectService) GetList() (*ProjectList, *Response, error) {
|
||||||
apiEndpoint := "rest/api/2/project"
|
apiEndpoint := "rest/api/2/project"
|
||||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -93,7 +92,7 @@ func (s *ProjectService) GetList() (*ProjectList, *http.Response, error) {
|
|||||||
// This can be an project id, or an project key.
|
// This can be an project id, or an project key.
|
||||||
//
|
//
|
||||||
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject
|
// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject
|
||||||
func (s *ProjectService) Get(projectID string) (*Project, *http.Response, error) {
|
func (s *ProjectService) Get(projectID string) (*Project, *Response, error) {
|
||||||
apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s", projectID)
|
apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s", projectID)
|
||||||
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
req, err := s.client.NewRequest("GET", apiEndpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user