Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions cla-backend-go/swagger/cla.v2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2768,6 +2768,8 @@ paths:
- $ref: "#/parameters/myClasGitlabId"
- $ref: "#/parameters/myClasGitlabUsername"
- $ref: "#/parameters/myClasGerritUsername"
- $ref: "#/parameters/pageSize"
- $ref: "#/parameters/offset"
responses:
'200':
description: 'Success'
Expand Down Expand Up @@ -2858,6 +2860,8 @@ paths:
- $ref: "#/parameters/myClasGitlabId"
- $ref: "#/parameters/myClasGitlabUsername"
- $ref: "#/parameters/myClasGerritUsername"
- $ref: "#/parameters/pageSize"
- $ref: "#/parameters/offset"
responses:
'200':
description: 'Success'
Expand Down Expand Up @@ -2941,6 +2945,8 @@ paths:
- $ref: "#/parameters/x-acl"
- $ref: "#/parameters/x-username"
- $ref: "#/parameters/x-email"
- $ref: "#/parameters/pageSize"
- $ref: "#/parameters/offset"
responses:
'200':
description: 'Success'
Expand Down Expand Up @@ -3154,6 +3160,8 @@ paths:
- $ref: "#/parameters/x-email"
- $ref: "#/parameters/path-companyID"
- $ref: "#/parameters/path-projectSFID"
- $ref: "#/parameters/pageSize"
- $ref: "#/parameters/offset"
responses:
'200':
description: 'Success'
Expand Down Expand Up @@ -4130,6 +4138,8 @@ paths:
- $ref: "#/parameters/x-username"
- $ref: "#/parameters/x-email"
- $ref: '#/parameters/path-companySFID'
- $ref: '#/parameters/pageSize'
- $ref: '#/parameters/offset'
produces:
- application/json
responses:
Expand All @@ -4150,6 +4160,42 @@ paths:
tags:
- company

/company/external/{companySFID}/cla-group/{claGroupID}/corporate-contributors:
get:
summary: List corporate contributors by company SFID
description: Returns the corporate contributors (employee acknowledgements) of the CLA Group for the company identified by its SFID - the same data as /cla-group/{claGroupID}/corporate-contributors on an organization-scoped path, so organization-scoped (CLA manager) tokens are authorized at the API gateway. The optional companyID query parameter selects one signing-entity record of the company - when omitted the parent record is used
Comment thread
lukaszgryglicki marked this conversation as resolved.
operationId: listCompanyClaGroupCorporateContributors
parameters:
- $ref: "#/parameters/x-request-id"
- $ref: "#/parameters/x-acl"
- $ref: "#/parameters/x-username"
- $ref: "#/parameters/x-email"
- $ref: '#/parameters/path-companySFID'
- $ref: "#/parameters/path-claGroupID"
- $ref: '#/parameters/companyID'
- $ref: '#/parameters/searchTerm'
- $ref: '#/parameters/pageSize'
- $ref: '#/parameters/nextKey'
responses:
'200':
description: 'Success'
headers:
x-request-id:
type: string
description: The unique request ID value - assigned/set by the API Gateway based on the session
schema:
$ref: '#/definitions/corporate-contributor-list'
'400':
$ref: '#/responses/invalid-request'
'403':
$ref: '#/responses/forbidden'
'404':
$ref: '#/responses/not-found'
'500':
$ref: '#/responses/internal-server-error'
tags:
- signatures

/company/name/{companyName}:
get:
summary: Gets the company by name
Expand Down Expand Up @@ -5157,6 +5203,13 @@ parameters:
required: false
minimum: 0
exclusiveMinimum: true
offset:
name: offset
description: The optional number of leading results to skip before assembling the page - when omitted no results are skipped
in: query
type: integer
required: false
minimum: 0
returnAllEvents:
name: returnAllEvents
description: The optional parameter which would avoid pagination and it will return all data
Expand Down
10 changes: 10 additions & 0 deletions cla-backend-go/swagger/common/cla-manager-request-list.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,13 @@ properties:
x-omitempty: false
items:
$ref: '#/definitions/cla-manager-request'
resultCount:
type: integer
format: int64
x-omitempty: false
description: Number of requests returned after optional pageSize/offset paging was applied
totalCount:
type: integer
format: int64
x-omitempty: false
description: Total number of requests before optional pageSize/offset paging was applied
5 changes: 5 additions & 0 deletions cla-backend-go/swagger/common/company-cla-groups.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ properties:
format: int64
x-omitempty: false
description: Number of entries returned
totalCount:
type: integer
format: int64
x-omitempty: false
description: Total number of entries before optional pageSize/offset paging was applied
list:
type: array
x-omitempty: false
Expand Down
5 changes: 5 additions & 0 deletions cla-backend-go/swagger/common/my-cla-list.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ properties:
format: int64
x-omitempty: false
description: Number of CLA records returned
totalCount:
type: integer
format: int64
x-omitempty: false
description: Total number of CLA records before optional pageSize/offset paging was applied
clas:
type: array
x-omitempty: false
Expand Down
5 changes: 5 additions & 0 deletions cla-backend-go/swagger/common/my-cla-manager-list.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,8 @@ properties:
format: int64
x-omitempty: false
description: Number of CLA managers returned
totalCount:
type: integer
format: int64
x-omitempty: false
description: Total number of CLA managers before optional pageSize/offset paging was applied
5 changes: 5 additions & 0 deletions cla-backend-go/swagger/common/my-identity-list.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ properties:
format: int64
x-omitempty: false
description: The number of identities returned
totalCount:
type: integer
format: int64
x-omitempty: false
description: Total number of identities before optional pageSize/offset paging was applied
identities:
type: array
x-omitempty: false
Expand Down
23 changes: 23 additions & 0 deletions cla-backend-go/utils/paging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright The Linux Foundation and each contributor to CommunityBridge.
// SPDX-License-Identifier: MIT

package utils

// PageBounds converts the optional pageSize/offset query parameters into
// [start, end) slice bounds for an in-memory list of totalCount entries.
// When both parameters are nil the full range is returned, keeping the
// non-paged behavior unchanged.
func PageBounds(totalCount int, pageSize, offset *int64) (int, int) {
start, end := 0, totalCount
if offset != nil && *offset > 0 {
if *offset >= int64(totalCount) {
start = totalCount
} else {
start = int(*offset)
}
}
if pageSize != nil && *pageSize > 0 && int64(end-start) > *pageSize {
end = start + int(*pageSize)
}
return start, end
}
41 changes: 41 additions & 0 deletions cla-backend-go/utils/paging_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright The Linux Foundation and each contributor to CommunityBridge.
// SPDX-License-Identifier: MIT

package utils

import (
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/stretchr/testify/assert"
)

func TestPageBounds(t *testing.T) {
cases := []struct {
name string
total int
pageSize *int64
offset *int64
wantStart int
wantEnd int
}{
{"no paging", 10, nil, nil, 0, 10},
{"page size only", 10, aws.Int64(3), nil, 0, 3},
{"offset only", 10, nil, aws.Int64(4), 4, 10},
{"page size and offset", 10, aws.Int64(3), aws.Int64(4), 4, 7},
{"last partial page", 10, aws.Int64(3), aws.Int64(9), 9, 10},
{"offset at end", 10, aws.Int64(3), aws.Int64(10), 10, 10},
{"offset beyond end", 10, aws.Int64(3), aws.Int64(42), 10, 10},
{"page size beyond end", 10, aws.Int64(100), nil, 0, 10},
{"zero page size ignored", 10, aws.Int64(0), nil, 0, 10},
{"zero offset ignored", 10, nil, aws.Int64(0), 0, 10},
{"empty list", 0, aws.Int64(5), aws.Int64(2), 0, 0},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
start, end := PageBounds(tc.total, tc.pageSize, tc.offset)
assert.Equal(t, tc.wantStart, start, "start")
assert.Equal(t, tc.wantEnd, end, "end")
})
}
}
2 changes: 1 addition & 1 deletion cla-backend-go/v2/cla_manager/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ func Configure(api *operations.EasyclaAPI, service Service, v1CompanyService v1C
return cla_manager.NewGetCLAManagerRequestsBadRequest().WithXRequestID(reqID).WithPayload(utils.ErrorResponseBadRequestWithError(reqID, msg, err))
}

requestList, err := service.GetCLAManagerRequests(ctx, v1CompanyModel, cginfo.ClaGroupID)
requestList, err := service.GetCLAManagerRequests(ctx, v1CompanyModel, cginfo.ClaGroupID, params.PageSize, params.Offset)
if err != nil {
msg := fmt.Sprintf("unable to lookup CLA Manager requests for Company ID: %s, Project ID: %s", params.CompanyID, params.ProjectSFID)
log.WithFields(f).WithError(err).Warn(msg)
Expand Down
2 changes: 1 addition & 1 deletion cla-backend-go/v2/cla_manager/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (f *fakeRequestsService) record(companyModel *v1Models.Company, claGroupID
f.claGroups = append(f.claGroups, claGroupID)
}

func (f *fakeRequestsService) GetCLAManagerRequests(_ context.Context, companyModel *v1Models.Company, claGroupID string) (*models.ClaManagerRequestList, error) {
func (f *fakeRequestsService) GetCLAManagerRequests(_ context.Context, companyModel *v1Models.Company, claGroupID string, _, _ *int64) (*models.ClaManagerRequestList, error) {
f.record(companyModel, claGroupID)
if f.list != nil || f.listErr != nil {
return f.list, f.listErr
Expand Down
17 changes: 16 additions & 1 deletion cla-backend-go/v2/cla_manager/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package cla_manager
import (
"context"
"fmt"
"sort"

"github.com/LF-Engineering/lfx-kit/auth"
"github.com/aws/aws-sdk-go/aws"
Expand All @@ -20,7 +21,7 @@ import (
)

// GetCLAManagerRequests returns the list of CLA manager requests for the given company and CLA group
func (s *service) GetCLAManagerRequests(ctx context.Context, companyModel *v1Models.Company, claGroupID string) (*models.ClaManagerRequestList, error) {
func (s *service) GetCLAManagerRequests(ctx context.Context, companyModel *v1Models.Company, claGroupID string, pageSize, offset *int64) (*models.ClaManagerRequestList, error) {
requestList, err := s.managerService.GetRequests(companyModel.CompanyID, claGroupID)
if err != nil {
return nil, err
Expand All @@ -30,6 +31,20 @@ func (s *service) GetCLAManagerRequests(ctx context.Context, companyModel *v1Mod
for i := range result.Requests {
result.Requests[i].CompanyExternalID = companyModel.CompanyExternalID
}
if pageSize != nil || offset != nil {
// deterministic order so the optional pageSize/offset windows are stable - the
// unpaged response keeps the stored (GSI) order
sort.Slice(result.Requests, func(i, j int) bool {
if result.Requests[i].Created != result.Requests[j].Created {
return result.Requests[i].Created < result.Requests[j].Created
}
return result.Requests[i].RequestID < result.Requests[j].RequestID
})
}
result.TotalCount = int64(len(result.Requests))
start, end := utils.PageBounds(len(result.Requests), pageSize, offset)
result.Requests = result.Requests[start:end]
Comment thread
lukaszgryglicki marked this conversation as resolved.
Comment thread
lukaszgryglicki marked this conversation as resolved.
result.ResultCount = int64(len(result.Requests))
return result, nil
}

Expand Down
51 changes: 46 additions & 5 deletions cla-backend-go/v2/cla_manager/requests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"

"github.com/LF-Engineering/lfx-kit/auth"
"github.com/aws/aws-sdk-go/aws"
"github.com/go-openapi/strfmt"
v1ClaManager "github.com/linuxfoundation/easycla/cla-backend-go/cla_manager"
"github.com/linuxfoundation/easycla/cla-backend-go/emails"
Expand Down Expand Up @@ -182,7 +183,7 @@ func TestGetCLAManagerRequests(t *testing.T) {
mgr := &fakeManagerService{requestList: &v1Models.ClaManagerRequestList{Requests: []v1Models.ClaManagerRequest{*pendingRequest()}}}
s := &service{managerService: mgr}

result, err := s.GetCLAManagerRequests(context.Background(), acmeCompany(), "cla-group-1")
result, err := s.GetCLAManagerRequests(context.Background(), acmeCompany(), "cla-group-1", nil, nil)
assert.Nil(t, err)
assert.Equal(t, [][]string{{"company-1", "cla-group-1"}}, mgr.listCalls)
if assert.Len(t, result.Requests, 1) {
Expand All @@ -208,21 +209,61 @@ func TestGetCLAManagerRequests(t *testing.T) {
mgr := &fakeManagerService{requestList: &v1Models.ClaManagerRequestList{}}
s := &service{managerService: mgr}

result, err := s.GetCLAManagerRequests(context.Background(), acmeCompany(), "cla-group-1")
result, err := s.GetCLAManagerRequests(context.Background(), acmeCompany(), "cla-group-1", nil, nil)
assert.Nil(t, err)
body, marshalErr := json.Marshal(result)
assert.Nil(t, marshalErr)
assert.JSONEq(t, `{"requests":[]}`, string(body))
assert.JSONEq(t, `{"requests":[],"resultCount":0,"totalCount":0}`, string(body))
})

t.Run("propagates the v1 service error", func(t *testing.T) {
mgr := &fakeManagerService{listErr: errors.New("dynamo down")}
s := &service{managerService: mgr}

result, err := s.GetCLAManagerRequests(context.Background(), acmeCompany(), "cla-group-1")
result, err := s.GetCLAManagerRequests(context.Background(), acmeCompany(), "cla-group-1", nil, nil)
assert.Nil(t, result)
assert.EqualError(t, err, "dynamo down")
})

t.Run("optional paging returns a stable window and the total count", func(t *testing.T) {
var reqs []v1Models.ClaManagerRequest
for _, rec := range []struct{ id, created string }{
{"req-c", "2026-01-03T00:00:00Z"},
{"req-a", "2026-01-01T00:00:00Z"},
{"req-b", "2026-01-02T00:00:00Z"},
} {
r := *pendingRequest()
r.RequestID = rec.id
r.Created = rec.created
reqs = append(reqs, r)
}
mgr := &fakeManagerService{requestList: &v1Models.ClaManagerRequestList{Requests: reqs}}
s := &service{managerService: mgr}

full, err := s.GetCLAManagerRequests(context.Background(), acmeCompany(), "cla-group-1", nil, nil)
assert.Nil(t, err)
assert.Equal(t, int64(3), full.TotalCount)
assert.Equal(t, int64(3), full.ResultCount)
if assert.Len(t, full.Requests, 3) {
assert.Equal(t, []string{"req-c", "req-a", "req-b"},
[]string{full.Requests[0].RequestID, full.Requests[1].RequestID, full.Requests[2].RequestID},
"the unpaged response keeps the stored order - no re-sorting without paging params")
}

page, err := s.GetCLAManagerRequests(context.Background(), acmeCompany(), "cla-group-1", aws.Int64(1), aws.Int64(1))
assert.Nil(t, err)
assert.Equal(t, int64(3), page.TotalCount)
assert.Equal(t, int64(1), page.ResultCount)
if assert.Len(t, page.Requests, 1) {
assert.Equal(t, "req-b", page.Requests[0].RequestID, "paged windows use the deterministic created-date order")
}

beyond, err := s.GetCLAManagerRequests(context.Background(), acmeCompany(), "cla-group-1", aws.Int64(5), aws.Int64(10))
assert.Nil(t, err)
assert.Equal(t, int64(3), beyond.TotalCount)
assert.Equal(t, int64(0), beyond.ResultCount)
assert.Len(t, beyond.Requests, 0)
})
}

func TestGetCLAManagerRequest(t *testing.T) {
Expand Down Expand Up @@ -437,5 +478,5 @@ func TestClaManagerRequestJSONContract(t *testing.T) {

listBody, err := json.Marshal(v2ClaManagerRequestList(nil))
assert.Nil(t, err)
assert.JSONEq(t, `{"requests":[]}`, string(listBody))
assert.JSONEq(t, `{"requests":[],"resultCount":0,"totalCount":0}`, string(listBody))
}
2 changes: 1 addition & 1 deletion cla-backend-go/v2/cla_manager/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ type Service interface {
CreateCLAManagerDesigneeByGroup(ctx context.Context, params cla_manager.CreateCLAManagerDesigneeByGroupParams, projectCLAGroups []*projects_cla_groups.ProjectClaGroup) ([]*models.ClaManagerDesignee, string, error)
ProjectCompanySignedOrNot(ctx context.Context, signedAtFoundation bool, projectCLAGroups []*projects_cla_groups.ProjectClaGroup, companyModel *v1Models.Company) error
IsCLAManagerDesignee(ctx context.Context, companySFID, claGroupID, userLFID string) (*models.UserRoleStatus, error)
GetCLAManagerRequests(ctx context.Context, companyModel *v1Models.Company, claGroupID string) (*models.ClaManagerRequestList, error)
GetCLAManagerRequests(ctx context.Context, companyModel *v1Models.Company, claGroupID string, pageSize, offset *int64) (*models.ClaManagerRequestList, error)
GetCLAManagerRequest(ctx context.Context, companyModel *v1Models.Company, claGroupID, requestID string) (*models.ClaManagerRequest, error)
ApproveCLAManagerRequest(ctx context.Context, authUser *auth.User, companyModel *v1Models.Company, claGroupID, requestID string) (*models.ClaManagerRequest, error)
DenyCLAManagerRequest(ctx context.Context, authUser *auth.User, companyModel *v1Models.Company, claGroupID, requestID string) (*models.ClaManagerRequest, error)
Expand Down
2 changes: 1 addition & 1 deletion cla-backend-go/v2/company/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func Configure(api *operations.EasyclaAPI, service Service, projectClaGroupRepo
return company.NewGetCompanyClaGroupsForbidden().WithXRequestID(reqID).WithPayload(utils.ErrorResponseForbidden(reqID, msg))
}

result, err := service.GetCompanyClaGroups(ctx, params.CompanySFID)
result, err := service.GetCompanyClaGroups(ctx, params.CompanySFID, params.PageSize, params.Offset)
if err != nil {
msg := fmt.Sprintf("unable to load CLA groups for company SFID: %s", params.CompanySFID)
log.WithFields(f).WithError(err).Warn(msg)
Expand Down
2 changes: 1 addition & 1 deletion cla-backend-go/v2/company/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type fakeCompanyService struct {
calls int
}

func (f *fakeCompanyService) GetCompanyClaGroups(_ context.Context, companySFID string) (*models.CompanyClaGroups, error) {
func (f *fakeCompanyService) GetCompanyClaGroups(_ context.Context, companySFID string, _, _ *int64) (*models.CompanyClaGroups, error) {
f.calls++
if f.err != nil {
return nil, f.err
Expand Down
Loading
Loading