diff --git a/cla-backend-go/cmd/server.go b/cla-backend-go/cmd/server.go index f5bcac285..d251021b4 100644 --- a/cla-backend-go/cmd/server.go +++ b/cla-backend-go/cmd/server.go @@ -305,7 +305,7 @@ func server(localMode bool) http.Handler { githubOrganizationsService := github_organizations.NewService(githubOrganizationsRepo, gitV1Repository, v1ProjectClaGroupRepo) gitlabOrganizationsService := gitlab_organizations.NewService(gitlabOrganizationRepo, v2RepositoriesService, v1ProjectClaGroupRepo, storeRepository, usersService, signaturesRepo, v1CompanyRepo) v1SignaturesService := signatures.NewService(signaturesRepo, v1CompanyService, usersService, eventsService, githubOrgValidation, v1RepositoriesService, githubOrganizationsService, v1ProjectService, gitlabApp, configFile.ClaV1ApiURL, configFile.CLALandingPage, configFile.CLALogoURL) - v2SignatureService := v2Signatures.NewService(awsSession, configFile.SignatureFilesBucket, v1ProjectService, v1CompanyService, v1SignaturesService, v1ProjectClaGroupRepo, signaturesRepo, usersService) + v2SignatureService := v2Signatures.NewService(awsSession, configFile.SignatureFilesBucket, v1ProjectService, v1CompanyService, v1SignaturesService, v1ProjectClaGroupRepo, signaturesRepo, usersService, eventsService) v1ClaManagerService := cla_manager.NewService(claManagerReqRepo, v1ProjectClaGroupRepo, v1CompanyService, v1ProjectService, usersService, v1SignaturesService, eventsService, emailTemplateService, configFile.CorporateConsoleV1URL) v2ClaManagerService := v2ClaManager.NewService(emailTemplateService, v1CompanyService, v1ProjectService, v1ClaManagerService, usersService, v1RepositoriesService, v2CompanyService, eventsService, v1ProjectClaGroupRepo) v1ApprovalListService := approval_list.NewService(approvalListRepo, v1ProjectClaGroupRepo, v1ProjectService, usersRepo, v1CompanyRepo, v1CLAGroupRepo, signaturesRepo, emailTemplateService, configFile.CorporateConsoleV2URL, http.DefaultClient) diff --git a/cla-backend-go/swagger/cla.v2.yaml b/cla-backend-go/swagger/cla.v2.yaml index 9ab98c12d..5ebd08f5f 100644 --- a/cla-backend-go/swagger/cla.v2.yaml +++ b/cla-backend-go/swagger/cla.v2.yaml @@ -2326,7 +2326,7 @@ paths: type: string description: The unique request ID value - assigned/set by the API Gateway based on the session schema: - $ref: '#/definitions/signatures' + $ref: '#/definitions/corporate-signatures' '400': $ref: '#/responses/invalid-request' '401': @@ -4916,6 +4916,15 @@ definitions: signature: $ref: './common/signature.yaml' + + corporate-signatures: + $ref: './common/corporate-signatures.yaml' + + corporate-signature: + $ref: './common/corporate-signature.yaml' + + approval-item: + $ref: './common/approval-item.yaml' icla-signatures: $ref: './common/icla-signatures.yaml' diff --git a/cla-backend-go/swagger/common/approval-item.yaml b/cla-backend-go/swagger/common/approval-item.yaml new file mode 100644 index 000000000..70d164255 --- /dev/null +++ b/cla-backend-go/swagger/common/approval-item.yaml @@ -0,0 +1,10 @@ + +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +properties: + approval_item: + type: string + date_added: + type: string \ No newline at end of file diff --git a/cla-backend-go/swagger/common/corporate-signature.yaml b/cla-backend-go/swagger/common/corporate-signature.yaml new file mode 100644 index 000000000..0e9db3594 --- /dev/null +++ b/cla-backend-go/swagger/common/corporate-signature.yaml @@ -0,0 +1,188 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +title: A signature model +description: A signature - may be an ICLA or CCLA signature +properties: + signatureID: + description: the signature ID + $ref: './common/properties/internal-id.yaml' + claType: + type: string + description: > + CLA Type field - identifies the specify signature type - individual, employee or corporate signature, valid options: + * `icla` - for individual contributor signature records (individuals not associated with a corporation) + * `ecla` - for employee contributor signature records (acknowledgements from corporate contributors) + * `ccla` - for corporate contributor signature records (created by CLA Signatories and managed by CLA Managers) + enum: [ icla,ecla,ccla ] + signatureCreated: + type: string + description: the signature record created time + example: '2019-05-03T18:59:13.082304+0000' + minLength: 18 + maxLength: 64 + signatureModified: + type: string + description: the signature modified created time + example: '2019-05-03T18:59:13.082304+0000' + minLength: 18 + maxLength: 64 + signatureSigned: + type: boolean + description: the signature signed flag - true or false value + example: true + x-omitempty: false + signatureApproved: + type: boolean + description: the signature approved flag - true or false value + example: true + x-omitempty: false + signatureReferenceType: + type: string + description: the signature reference type - either user or company + example: 'user' + minLength: 2 + maxLength: 12 + signatureReferenceID: + description: the signature reference ID which references a compnay ID or user ID + $ref: './common/properties/internal-id.yaml' + signatureReferenceName: + type: string + signatureReferenceNameLower: + type: string + signatureType: + type: string + description: the signature type - either cla or ccla + example: 'ccla' + minLength: 2 + maxLength: 12 + signedOn: + type: string + signatoryName: + type: string + signatureACL: + type: array + items: + $ref: '#/definitions/user' + userName: + type: string + companyName: + $ref: './common/properties/company-name.yaml' + signingEntityName: + $ref: './common/properties/company-signing-entity-name.yaml' + projectID: + type: string + description: the CLA Group ID + userGHID: + type: string + description: the user's GitHub ID, when available + example: '13434323' + userGHUsername: + type: string + description: the user's GitHub username, when available + example: 'github-user' + userGitlabID: + type: string + description: the user's Gitlab ID, when available + example: '1864' + userGitlabUsername: + type: string + description: the user's Gitlab username, when available + example: 'gitlab-user' + userLFID: + type: string + description: the user's LF Login ID + example: abc1234 + version: + type: string + description: the version of the signature record + example: v1 + minLength: 2 + maxLength: 12 + created: + type: string + description: the date/time when this signature record was created + example: '2017-04-19T16:42:00.000000+0000' + modified: + type: string + description: the date/time when this signature record was last modified + example: '2019-07-15T15:28:33.127118+0000' + signatureMajorVersion: + type: string + description: the signature major version number + example: '2' + signatureMinorVersion: + type: string + description: the signature minor version number + example: '1' + signatureDocumentMajorVersion: + type: string + description: the signature documentt major version + signatureDocumentMinorVersion: + type: string + description: the signature document minor version + signatureSignURL: + type: string + description: the signature Document Sign URL + sigTypeSignedApprovedId: + type: string + signatureCallbackURL: + type: string + description: the signature callback URL + signatureReturnURL: + type: string + description: the signature return URL + signatureReturnURLType: + type: string + description: the signature return URL type + signatureEnvelopeId: + type: string + description: the signature envelope ID + emailApprovalList: + type: array + description: a list of zero or more email addresses in the approval list + x-nullable: true + items: + $ref: "#/definitions/approval-item" + domainApprovalList: + type: array + description: a list of zero or more domains in the approval list + x-nullable: true + items: + $ref: "#/definitions/approval-item" + githubUsernameApprovalList: + type: array + description: a list of zero or more GitHub user name values in the approval list + x-nullable: true + items: + $ref: "#/definitions/approval-item" + githubOrgApprovalList: + type: array + description: a list of zero or more GitHub organization values in the approval list + x-nullable: true + items: + $ref: "#/definitions/approval-item" + gitlabUsernameApprovalList: + type: array + description: a list of zero or more Gitlab user name values in the approval list + x-nullable: true + items: + $ref: "#/definitions/approval-item" + gitlabOrgApprovalList: + type: array + description: a list of zero or more Gitlab organization values in the approval list + x-nullable: true + items: + $ref: "#/definitions/approval-item" + userDocusignName: + type: string + description: full name used on docusign document + userDocusignDateSigned: + type: string + description: docusign signature date + autoCreateECLA: + type: boolean + description: flag to indicate if the product should automatically create an employee acknowledgement for a given user when the CLA manager adds the user to the email, GitLab username, or GitLab username approval list + example: true + x-omitempty: false diff --git a/cla-backend-go/swagger/common/corporate-signatures.yaml b/cla-backend-go/swagger/common/corporate-signatures.yaml new file mode 100644 index 000000000..b0db7fc26 --- /dev/null +++ b/cla-backend-go/swagger/common/corporate-signatures.yaml @@ -0,0 +1,25 @@ +# Copyright The Linux Foundation and each contributor to CommunityBridge. +# SPDX-License-Identifier: MIT + +type: object +x-nullable: false +title: Signatures +description: Signatures +properties: + projectID: + type: string + resultCount: + type: integer + format: int64 + x-omitempty: false + totalCount: + type: integer + format: int64 + x-omitempty: false + lastKeyScanned: + type: string + signatures: + type: array + x-omitempty: false + items: + $ref: '#/definitions/corporate-signature' \ No newline at end of file diff --git a/cla-backend-go/v2/signatures/converters.go b/cla-backend-go/v2/signatures/converters.go index 55e3604e8..44191e60a 100644 --- a/cla-backend-go/v2/signatures/converters.go +++ b/cla-backend-go/v2/signatures/converters.go @@ -6,8 +6,11 @@ package signatures import ( "fmt" "strings" + "sync" + "time" v1Models "github.com/communitybridge/easycla/cla-backend-go/gen/v1/models" + "github.com/communitybridge/easycla/cla-backend-go/gen/v1/restapi/operations/events" "github.com/communitybridge/easycla/cla-backend-go/gen/v2/models" log "github.com/communitybridge/easycla/cla-backend-go/logging" "github.com/communitybridge/easycla/cla-backend-go/utils" @@ -50,6 +53,116 @@ func v2SignaturesReplaceCompanyID(src *v1Models.Signatures, internalID, external return &dst, nil } +func (s *Service) v2SignaturesToCorporateSignatures(src models.Signatures, projectSFID string) (*models.CorporateSignatures, error) { + var dst models.CorporateSignatures + err := copier.Copy(&dst, src) + if err != nil { + return nil, err + } + + // Convert the individual signatures + for _, sigSrc := range src.Signatures { + for _, sigDest := range dst.Signatures { + err = s.TransformSignatureToCorporateSignature(sigSrc, sigDest, projectSFID) + if err != nil { + return nil, err + } + } + } + + return &dst, nil +} + +// TransformSignatureToCorporateSignature transforms a Signature model into a CorporateSignature model +func (s *Service) TransformSignatureToCorporateSignature(signature *models.Signature, corporateSignature *models.CorporateSignature, projectSFID string) error { + f := logrus.Fields{ + "functionName": "TransformSignatureToCorporateSignature", + "signatureID": signature.SignatureID, + } + + var wg sync.WaitGroup + var errMutex sync.Mutex + var err error + + transformApprovalList := func(approvalList []string, listType string, destinationList *[]*models.ApprovalItem) { + defer wg.Done() + for _, item := range approvalList { + searchTerm := fmt.Sprintf("%s was added to the approval list", item) + + pageSize := int64(10000) + result, eventErr := s.eventService.SearchEvents(&events.SearchEventsParams{ + SearchTerm: &searchTerm, + ProjectID: &projectSFID, + PageSize: &pageSize, + }) + if eventErr != nil { + errMutex.Lock() + err = eventErr + errMutex.Unlock() + return + } + approvalItem := &models.ApprovalItem{ + ApprovalItem: item, + } + if len(result.Events) > 0 { + event := getLatestEvent(result.Events) + approvalItem.DateAdded = event.EventTime + } else { + log.WithFields(f).Debugf("no events found for %s: %s", listType, item) + } + *destinationList = append(*destinationList, approvalItem) + } + } + + // Transform domain approval list + wg.Add(1) + go transformApprovalList(signature.DomainApprovalList, "domain", &corporateSignature.DomainApprovalList) + + // Transform email approval list + wg.Add(1) + go transformApprovalList(signature.EmailApprovalList, "email", &corporateSignature.EmailApprovalList) + + // Transform GitHub org approval list + wg.Add(1) + go transformApprovalList(signature.GithubOrgApprovalList, "githubOrg", &corporateSignature.GithubOrgApprovalList) + + // Transform GitHub username approval list + wg.Add(1) + go transformApprovalList(signature.GithubUsernameApprovalList, "githubUsername", &corporateSignature.GithubUsernameApprovalList) + + // Transform GitLab org approval list + wg.Add(1) + go transformApprovalList(signature.GitlabOrgApprovalList, "gitlabOrg", &corporateSignature.GitlabOrgApprovalList) + + // Transform GitLab username approval list + wg.Add(1) + go transformApprovalList(signature.GitlabUsernameApprovalList, "gitlabUsername", &corporateSignature.GitlabUsernameApprovalList) + + wg.Wait() + + return err +} + +func getLatestEvent(events []*v1Models.Event) *v1Models.Event { + var latest *v1Models.Event + var latestTime time.Time + + for _, item := range events { + t, err := utils.ParseDateTime(item.EventTime) + if err != nil { + log.Debugf("Error parsing time: %+v ", err) + continue + } + + if latest == nil || t.After(latestTime) { + latest = item + latestTime = t + } + } + + return latest +} + func iclaSigCsvHeader() string { return `Name,GitHub Username,GitLab Username,LF_ID,Email,Signed Date,Approved,Signed` } diff --git a/cla-backend-go/v2/signatures/service.go b/cla-backend-go/v2/signatures/service.go index 44c99be5a..b83da4a33 100644 --- a/cla-backend-go/v2/signatures/service.go +++ b/cla-backend-go/v2/signatures/service.go @@ -47,7 +47,7 @@ var ( // ServiceInterface contains method of v2 signature service type ServiceInterface interface { - GetProjectCompanySignatures(ctx context.Context, companyID, companySFID, projectSFID string) (*models.Signatures, error) + GetProjectCompanySignatures(ctx context.Context, companyID, companySFID, projectSFID string) (*models.CorporateSignatures, error) GetProjectIclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) GetProjectCclaSignaturesCsv(ctx context.Context, claGroupID string) ([]byte, error) GetProjectIclaSignatures(ctx context.Context, claGroupID string, searchTerm *string, approved, signed *bool, pageSize int64, nextKey string, withExtraDetails bool) (*models.IclaSignatures, error) @@ -70,13 +70,14 @@ type Service struct { projectsClaGroupsRepo projects_cla_groups.Repository s3 *s3.S3 signaturesBucket string + eventService events.Service } // NewService creates instance of v2 signature service func NewService(awsSession *session.Session, signaturesBucketName string, v1ProjectService service.Service, v1CompanyService company.IService, v1SignatureService signatures.SignatureService, - pcgRepo projects_cla_groups.Repository, v1SignatureRepo signatures.SignatureRepository, usersService users.Service) *Service { + pcgRepo projects_cla_groups.Repository, v1SignatureRepo signatures.SignatureRepository, usersService users.Service, eventService events.Service) *Service { return &Service{ v1ProjectService: v1ProjectService, v1CompanyService: v1CompanyService, @@ -86,11 +87,12 @@ func NewService(awsSession *session.Session, signaturesBucketName string, v1Proj projectsClaGroupsRepo: pcgRepo, s3: s3.New(awsSession), signaturesBucket: signaturesBucketName, + eventService: eventService, } } // GetProjectCompanySignatures return the signatures for the specified project and company information -func (s *Service) GetProjectCompanySignatures(ctx context.Context, companyID, companySFID, projectSFID string) (*models.Signatures, error) { +func (s *Service) GetProjectCompanySignatures(ctx context.Context, companyID, companySFID, projectSFID string) (*models.CorporateSignatures, error) { pm, err := s.projectsClaGroupsRepo.GetClaGroupIDForProject(ctx, projectSFID) if err != nil { return nil, err @@ -110,7 +112,12 @@ func (s *Service) GetProjectCompanySignatures(ctx context.Context, companyID, co resp.ProjectID = sig.ProjectID resp.Signatures = append(resp.Signatures, sig) } - return v2SignaturesReplaceCompanyID(resp, companyID, companySFID) + oldformatSignatures, err := v2SignaturesReplaceCompanyID(resp, companyID, companySFID) + if err != nil { + return nil, err + } + + return s.v2SignaturesToCorporateSignatures(*oldformatSignatures, projectSFID) } // eclaSigCsvLine returns a single ECLA signature CSV line