Skip to content
Merged
75 changes: 74 additions & 1 deletion github/enterprise_budgets.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,49 @@ type EnterpriseListBudgets struct {
TotalCount *int `json:"total_count,omitempty"`
}

// EnterpriseListBudgetsOptions specifies the optional parameters to the
// EnterpriseService.ListBudgets method.
type EnterpriseListBudgetsOptions struct {
Scope string `url:"scope,omitempty"`
User string `url:"user,omitempty"`

ListOptions
}

// EnterpriseBudgetUserState represents the budget status and consumption of an individual user.
type EnterpriseBudgetUserState struct {
User *string `json:"user,omitempty"`
ConsumedAmount float64 `json:"consumed_amount"`
TargetAmount float64 `json:"target_amount"`
OverrideBudgetID *string `json:"override_budget_id,omitempty"`
}
Comment thread
devaniketh marked this conversation as resolved.

func (u EnterpriseBudgetUserState) String() string {
return Stringify(u)
}

// EnterpriseBudgetUserStates represents the response when retrieving user states for a budget.
type EnterpriseBudgetUserStates struct {
UserStates []*EnterpriseBudgetUserState `json:"user_states"`
HasNextPage bool `json:"has_next_page"`
TotalCount int `json:"total_count"`
}
Comment thread
devaniketh marked this conversation as resolved.

func (u EnterpriseBudgetUserStates) String() string {
return Stringify(u)
}

// EnterpriseGetUserStatesOptions specifies the optional parameters to the
// EnterpriseService.GetUserStatesForBudget method.
type EnterpriseGetUserStatesOptions struct {
SortOrder int `url:"sort_order,omitempty"`
User string `url:"user,omitempty"`
ThresholdLowerBound float64 `url:"threshold_lower_bound,omitempty"`
ThresholdUpperBound float64 `url:"threshold_upper_bound,omitempty"`

ListOptions
}

// EnterpriseCreateBudget represents the payload to create a GitHub enterprise budget.
type EnterpriseCreateBudget struct {
BudgetAmount int `json:"budget_amount"`
Expand Down Expand Up @@ -92,8 +135,12 @@ type EnterpriseDeleteBudgetResponse struct {
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/billing/budgets?apiVersion=2022-11-28#get-all-budgets
//
//meta:operation GET /enterprises/{enterprise}/settings/billing/budgets
func (s *EnterpriseService) ListBudgets(ctx context.Context, enterprise string) (*EnterpriseListBudgets, *Response, error) {
func (s *EnterpriseService) ListBudgets(ctx context.Context, enterprise string, opts *EnterpriseListBudgetsOptions) (*EnterpriseListBudgets, *Response, error) {
u := fmt.Sprintf("enterprises/%v/settings/billing/budgets", enterprise)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}

req, err := s.client.NewRequest(ctx, "GET", u, nil)
if err != nil {
Expand All @@ -109,6 +156,32 @@ func (s *EnterpriseService) ListBudgets(ctx context.Context, enterprise string)
return budgets, resp, nil
}

// GetUserStatesForBudget gets user states for a multi-user budget in an enterprise.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/billing/budgets?apiVersion=2022-11-28#get-user-states-for-a-multi-user-budget
//
//meta:operation GET /enterprises/{enterprise}/settings/billing/budgets/{budget_id}/user-states
func (s *EnterpriseService) GetUserStatesForBudget(ctx context.Context, enterprise, budgetID string, opts *EnterpriseGetUserStatesOptions) (*EnterpriseBudgetUserStates, *Response, error) {
u := fmt.Sprintf("enterprises/%v/settings/billing/budgets/%v/user-states", enterprise, budgetID)
u, err := addOptions(u, opts)
Comment thread
devaniketh marked this conversation as resolved.
if err != nil {
return nil, nil, err
}

req, err := s.client.NewRequest(ctx, "GET", u, nil)
if err != nil {
return nil, nil, err
}

var userStates *EnterpriseBudgetUserStates
resp, err := s.client.Do(req, &userStates)
if err != nil {
return nil, resp, err
}

return userStates, resp, nil
}

// CreateBudget creates a new budget for an enterprise.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/billing/budgets?apiVersion=2022-11-28#create-a-budget
Expand Down
110 changes: 106 additions & 4 deletions github/enterprise_budgets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ func TestEnterpriseService_ListBudgets(t *testing.T) {

mux.HandleFunc("/enterprises/e/settings/billing/budgets", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{
"page": "2",
"per_page": "10",
"scope": "enterprise",
"user": "octocat",
})
fmt.Fprint(w, `{
"budgets": [
{
Expand All @@ -39,8 +45,13 @@ func TestEnterpriseService_ListBudgets(t *testing.T) {
}`)
})

opts := &EnterpriseListBudgetsOptions{
Scope: "enterprise",
User: "octocat",
ListOptions: ListOptions{Page: 2, PerPage: 10},
}
ctx := t.Context()
budgets, _, err := client.Enterprise.ListBudgets(ctx, "e")
budgets, _, err := client.Enterprise.ListBudgets(ctx, "e", opts)
if err != nil {
t.Errorf("Enterprise.ListBudgets returned error: %v", err)
}
Expand Down Expand Up @@ -69,15 +80,15 @@ func TestEnterpriseService_ListBudgets(t *testing.T) {

const methodName = "ListBudgets"
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
got, resp, err := client.Enterprise.ListBudgets(ctx, "e")
got, resp, err := client.Enterprise.ListBudgets(ctx, "e", nil)
if got != nil {
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
}
return resp, err
})

testBadOptions(t, methodName, func() (err error) {
_, _, err = client.Enterprise.ListBudgets(ctx, "\n")
_, _, err = client.Enterprise.ListBudgets(ctx, "\n", opts)
return err
})
}
Expand All @@ -87,7 +98,98 @@ func TestEnterpriseService_ListBudgets_invalidEnterprise(t *testing.T) {
client, _, _ := setup(t)

ctx := t.Context()
_, _, err := client.Enterprise.ListBudgets(ctx, "%")
_, _, err := client.Enterprise.ListBudgets(ctx, "%", nil)
testURLParseError(t, err)
}

func TestEnterpriseService_GetUserStatesForBudget(t *testing.T) {
t.Parallel()
client, mux, _ := setup(t)

mux.HandleFunc("/enterprises/e/settings/billing/budgets/b-123/user-states", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{
"page": "1",
"per_page": "2",
"sort_order": "1",
"user": "octocat",
"threshold_lower_bound": "50.5",
"threshold_upper_bound": "100",
})
fmt.Fprint(w, `{
"user_states": [
{
"user": "octocat",
"consumed_amount": 50.5,
"target_amount": 1000
},
{
"user": "monalisa",
"consumed_amount": 250,
"target_amount": 1000,
"override_budget_id": "2066deda-923f-43f9-88d2-62395a28c0cdd"
}
],
"has_next_page": false,
"total_count": 2
}`)
})

opts := &EnterpriseGetUserStatesOptions{
SortOrder: 1,
User: "octocat",
ThresholdLowerBound: 50.5,
ThresholdUpperBound: 100.0,
ListOptions: ListOptions{Page: 1, PerPage: 2},
}
ctx := t.Context()
states, _, err := client.Enterprise.GetUserStatesForBudget(ctx, "e", "b-123", opts)
if err != nil {
t.Errorf("Enterprise.GetUserStatesForBudget returned error: %v", err)
}

want := &EnterpriseBudgetUserStates{
UserStates: []*EnterpriseBudgetUserState{
{
User: new("octocat"),
ConsumedAmount: 50.5,
TargetAmount: 1000.0,
},
{
User: new("monalisa"),
ConsumedAmount: 250.0,
TargetAmount: 1000.0,
OverrideBudgetID: new("2066deda-923f-43f9-88d2-62395a28c0cdd"),
},
},
HasNextPage: false,
TotalCount: 2,
}
if !cmp.Equal(states, want) {
t.Errorf("Enterprise.GetUserStatesForBudget returned %+v, want %+v", states, want)
}

const methodName = "GetUserStatesForBudget"
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
got, resp, err := client.Enterprise.GetUserStatesForBudget(ctx, "e", "b-123", nil)
if got != nil {
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
}
return resp, err
})

testBadOptions(t, methodName, func() (err error) {
_, _, err = client.Enterprise.GetUserStatesForBudget(ctx, "\n", "\n", opts)
return err
})
Comment thread
gmlewis marked this conversation as resolved.
}

func TestEnterpriseService_GetUserStatesForBudget_invalidEnterprise(t *testing.T) {
t.Parallel()
client, _, _ := setup(t)

ctx := t.Context()
_, _, err := client.Enterprise.GetUserStatesForBudget(ctx, "%", "b-123", nil)
testURLParseError(t, err)
}

Expand Down
7 changes: 4 additions & 3 deletions github/gen-iterators.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,10 @@ var useCursorPagination = map[string]bool{

// customNames provides custom names for iterator methods where the default methodName + "Iter" would be confusing.
var customNames = map[string]string{
"RepositoriesService.GetCommit": "ListCommitFiles",
"RepositoriesService.CompareCommits": "ListCommitComparisonFiles",
"RepositoriesService.GetCombinedStatus": "ListCombinedStatus",
"EnterpriseService.GetUserStatesForBudget": "ListUserStatesForBudget",
"RepositoriesService.GetCommit": "ListCommitFiles",
"RepositoriesService.CompareCommits": "ListCommitComparisonFiles",
"RepositoriesService.GetCombinedStatus": "ListCombinedStatus",
}

// sliceToBeUsedForIteration identifies methods where the wrapper struct contains multiple []*T fields,
Expand Down
2 changes: 1 addition & 1 deletion github/gen-stringify-test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ var (
return "false"
case "new(false)":
return "false"
case "new(0.0)":
case "0.0", "new(0.0)":
return "0"
case "0", "new(0)", "new(int64(0))":
return "0"
Expand Down
104 changes: 104 additions & 0 deletions github/github-accessors.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading