Skip to content

Commit

Permalink
Check for issue activity in Maintained (#1251)
Browse files Browse the repository at this point in the history
Co-authored-by: Azeem Shaikh <[email protected]>
  • Loading branch information
azeemshaikh38 and azeemsgoogle authored Nov 12, 2021
1 parent 1775025 commit 51de6b6
Show file tree
Hide file tree
Showing 15 changed files with 170 additions and 35 deletions.
13 changes: 12 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ GIT_VERSION ?= $(shell git describe --tags --always --dirty)
SOURCE_DATE_EPOCH=$(shell git log --date=iso8601-strict -1 --pretty=%ct)
GOLANGGCI_LINT := golangci-lint
PROTOC_GEN_GO := protoc-gen-go
MOCKGEN := mockgen
PROTOC := $(shell which protoc)
IMAGE_NAME = scorecard
OUTPUT = output
Expand Down Expand Up @@ -96,7 +97,7 @@ tree-status: ## Verify tree is clean and all changes are committed
build-cron: build-pubsub build-bq-transfer build-github-server build-webhook build-add-script \
build-validate-script build-update-script

build-targets = generate-docs build-proto build-scorecard build-cron ko-build-everything dockerbuild
build-targets = generate-mocks generate-docs build-proto build-scorecard build-cron ko-build-everything dockerbuild
.PHONY: build $(build-targets)
build: ## Build all binaries and images in the repo.
build: $(build-targets)
Expand All @@ -108,6 +109,16 @@ cron/data/request.pb.go: cron/data/request.proto | $(PROTOC)
cron/data/metadata.pb.go: cron/data/metadata.proto | $(PROTOC)
protoc --go_out=../../../ cron/data/metadata.proto

generate-mocks: ## Compiles and generates all mocks using mockgen.
generate-mocks: clients/mockrepo/client.go clients/mockrepo/repo.go
clients/mockrepo/client.go: clients/repo_client.go
# Generating MockRepoClient
$(MOCKGEN) -source=clients/repo_client.go -destination clients/mockrepo/client.go -package mockrepo -copyright_file clients/mockrepo/license.txt
clients/mockrepo/repo.go: clients/repo.go
# Generating MockRepoClient
$(MOCKGEN) -source=clients/repo.go -destination clients/mockrepo/repo.go -package mockrepo -copyright_file clients/mockrepo/license.txt


generate-docs: ## Generates docs
generate-docs: validate-docs docs/checks.md
docs/checks.md: docs/checks/internal/checks.yaml docs/checks/internal/*.go docs/checks/internal/generate/*.go
Expand Down
32 changes: 21 additions & 11 deletions checks/maintained.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const (
// CheckMaintained is the exported check name for Maintained.
CheckMaintained = "Maintained"
lookBackDays = 90
commitsPerWeek = 1
activityPerWeek = 1
daysInOneWeek = 7
)

Expand All @@ -46,25 +46,35 @@ func IsMaintained(c *checker.CheckRequest) checker.CheckResult {
return checker.CreateMinScoreResult(CheckMaintained, "repo is marked as archived")
}

// If not explicitly marked archived, look for activity in past `lookBackDays`.
threshold := time.Now().AddDate(0 /*years*/, 0 /*months*/, -1*lookBackDays /*days*/)

commits, err := c.RepoClient.ListCommits()
if err != nil {
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
return checker.CreateRuntimeErrorResult(CheckMaintained, e)
}
commitsWithinThreshold := 0
for _, commit := range commits {
if commit.CommittedDate.After(threshold) {
commitsWithinThreshold++
}
}

tz, err := time.LoadLocation("UTC")
issues, err := c.RepoClient.ListIssues()
if err != nil {
e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("time.LoadLocation: %v", err))
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
return checker.CreateRuntimeErrorResult(CheckMaintained, e)
}
threshold := time.Now().In(tz).AddDate(0, 0, -1*lookBackDays)
totalCommits := 0
for _, commit := range commits {
if commit.CommittedDate.After(threshold) {
totalCommits++
issuesUpdatedWithinThreshold := 0
for _, issue := range issues {
if issue.UpdatedAt.After(threshold) {
issuesUpdatedWithinThreshold++
}
}
return checker.CreateProportionalScoreResult(CheckMaintained,
fmt.Sprintf("%d commit(s) found in the last %d days", totalCommits, lookBackDays),
totalCommits, commitsPerWeek*lookBackDays/daysInOneWeek)

return checker.CreateProportionalScoreResult(CheckMaintained, fmt.Sprintf(
"%d commit(s) out of %d and %d issue activity out of %d found in the last %d days",
commitsWithinThreshold, len(commits), issuesUpdatedWithinThreshold, len(issues), lookBackDays),
commitsWithinThreshold+issuesUpdatedWithinThreshold, activityPerWeek*lookBackDays/daysInOneWeek)
}
19 changes: 0 additions & 19 deletions clients/githubrepo/branches.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,25 +114,6 @@ func (handler *branchesHandler) listBranches() ([]*clients.BranchRef, error) {
return handler.branches, nil
}

func copyBoolPtr(src *bool, dest **bool) {
if src != nil {
*dest = new(bool)
**dest = *src
}
}

func copyInt32Ptr(src *int32, dest **int32) {
if src != nil {
*dest = new(int32)
**dest = *src
}
}

func copyStringSlice(src []string, dest *[]string) {
*dest = make([]string, len(src))
copy(*dest, src)
}

func getBranchRefFrom(data branch) *clients.BranchRef {
branchRef := new(clients.BranchRef)
if data.Name != nil {
Expand Down
5 changes: 5 additions & 0 deletions clients/githubrepo/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ func (client *Client) ListCommits() ([]clients.Commit, error) {
return client.graphClient.getCommits()
}

// ListIssues implements RepoClient.ListIssues.
func (client *Client) ListIssues() ([]clients.Issue, error) {
return client.graphClient.getIssues()
}

// ListReleases implements RepoClient.ListReleases.
func (client *Client) ListReleases() ([]clients.Release, error) {
return client.releases.getReleases()
Expand Down
50 changes: 50 additions & 0 deletions clients/githubrepo/copy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2021 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package githubrepo

import "time"

func copyBoolPtr(src *bool, dest **bool) {
if src != nil {
*dest = new(bool)
**dest = *src
}
}

func copyStringPtr(src *string, dest **string) {
if src != nil {
*dest = new(string)
**dest = *src
}
}

func copyInt32Ptr(src *int32, dest **int32) {
if src != nil {
*dest = new(int32)
**dest = *src
}
}

func copyTimePtr(src *time.Time, dest **time.Time) {
if src != nil {
*dest = new(time.Time)
**dest = *src
}
}

func copyStringSlice(src []string, dest *[]string) {
*dest = make([]string, len(src))
copy(*dest, src)
}
30 changes: 30 additions & 0 deletions clients/githubrepo/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"fmt"
"sync"
"time"

"github.com/shurcooL/githubv4"

Expand All @@ -27,6 +28,7 @@ import (

const (
pullRequestsToAnalyze = 30
issuesToAnalyze = 30
reviewsToAnalyze = 30
labelsToAnalyze = 30
commitsToAnalyze = 30
Expand Down Expand Up @@ -81,6 +83,13 @@ type graphqlData struct {
} `graphql:"reviews(last: $reviewsToAnalyze)"`
}
} `graphql:"pullRequests(last: $pullRequestsToAnalyze, states: MERGED)"`
Issues struct {
Nodes []struct {
// nolint: revive,stylecheck // naming according to githubv4 convention.
Url *string
UpdatedAt *time.Time
}
} `graphql:"issues(first: $issuesToAnalyze, orderBy:{field:UPDATED_AT, direction:DESC})"`
} `graphql:"repository(owner: $owner, name: $name)"`
}

Expand All @@ -94,6 +103,7 @@ type graphqlHandler struct {
repo string
prs []clients.PullRequest
commits []clients.Commit
issues []clients.Issue
archived bool
}

Expand All @@ -112,6 +122,7 @@ func (handler *graphqlHandler) setup() error {
"owner": githubv4.String(handler.owner),
"name": githubv4.String(handler.repo),
"pullRequestsToAnalyze": githubv4.Int(pullRequestsToAnalyze),
"issuesToAnalyze": githubv4.Int(issuesToAnalyze),
"reviewsToAnalyze": githubv4.Int(reviewsToAnalyze),
"labelsToAnalyze": githubv4.Int(labelsToAnalyze),
"commitsToAnalyze": githubv4.Int(commitsToAnalyze),
Expand All @@ -122,6 +133,7 @@ func (handler *graphqlHandler) setup() error {
handler.archived = bool(handler.data.Repository.IsArchived)
handler.prs = pullRequestsFrom(handler.data)
handler.commits = commitsFrom(handler.data)
handler.issues = issuesFrom(handler.data)
})
return handler.errSetup
}
Expand All @@ -140,6 +152,13 @@ func (handler *graphqlHandler) getCommits() ([]clients.Commit, error) {
return handler.commits, nil
}

func (handler *graphqlHandler) getIssues() ([]clients.Issue, error) {
if err := handler.setup(); err != nil {
return nil, fmt.Errorf("error during graphqlHandler.setup: %w", err)
}
return handler.issues, nil
}

func (handler *graphqlHandler) isArchived() (bool, error) {
if err := handler.setup(); err != nil {
return false, fmt.Errorf("error during graphqlHandler.setup: %w", err)
Expand Down Expand Up @@ -193,3 +212,14 @@ func commitsFrom(data *graphqlData) []clients.Commit {
}
return ret
}

func issuesFrom(data *graphqlData) []clients.Issue {
var ret []clients.Issue
for _, issue := range data.Repository.Issues.Nodes {
var tmpIssue clients.Issue
copyStringPtr(issue.Url, &tmpIssue.URI)
copyTimePtr(issue.UpdatedAt, &tmpIssue.UpdatedAt)
ret = append(ret, tmpIssue)
}
return ret
}
23 changes: 23 additions & 0 deletions clients/issue.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2021 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package clients

import "time"

// Issue represents a thread like GitHub issue comment thread.
type Issue struct {
URI *string
UpdatedAt *time.Time
}
6 changes: 6 additions & 0 deletions clients/localdir/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,16 @@ func (client *localDirClient) GetDefaultBranch() (*clients.BranchRef, error) {
return nil, fmt.Errorf("GetDefaultBranch: %w", clients.ErrUnsupportedFeature)
}

// ListCommits implements RepoClient.ListCommits.
func (client *localDirClient) ListCommits() ([]clients.Commit, error) {
return nil, fmt.Errorf("ListCommits: %w", clients.ErrUnsupportedFeature)
}

// ListIssues implements RepoClient.ListIssues.
func (client *localDirClient) ListIssues() ([]clients.Issue, error) {
return nil, fmt.Errorf("ListIssues: %w", clients.ErrUnsupportedFeature)
}

// ListReleases implements RepoClient.ListReleases.
func (client *localDirClient) ListReleases() ([]clients.Release, error) {
return nil, fmt.Errorf("ListReleases: %w", clients.ErrUnsupportedFeature)
Expand Down
17 changes: 16 additions & 1 deletion clients/mockrepo/client.go

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

1 change: 1 addition & 0 deletions clients/repo_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type RepoClient interface {
ListBranches() ([]*BranchRef, error)
GetDefaultBranch() (*BranchRef, error)
ListCommits() ([]Commit, error)
ListIssues() ([]Issue, error)
ListReleases() ([]Release, error)
ListContributors() ([]Contributor, error)
ListSuccessfulWorkflowRuns(filename string) ([]WorkflowRun, error)
Expand Down
1 change: 1 addition & 0 deletions docs/checks/internal/validate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ var (
"ListBranches": {"GitHub"},
"GetDefaultBranch": {"GitHub"},
"ListCommits": {"GitHub"},
"ListIssues": {"GitHub"},
"ListReleases": {"GitHub"},
"ListContributors": {"GitHub"},
"ListSuccessfulWorkflowRuns": {"GitHub"},
Expand Down
2 changes: 0 additions & 2 deletions e2e/maintained_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package e2e

import (
"context"
"fmt"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -54,7 +53,6 @@ var _ = Describe("E2E TEST:"+checks.CheckMaintained, func() {
// Old version.
Expect(result.Error).Should(BeNil())
Expect(result.Pass).Should(BeTrue())
fmt.Printf("%v", result)
// New version.
Expect(scut.ValidateTestReturn(nil, "active repo", &expected, &result, &dl)).Should(BeTrue())
})
Expand Down
1 change: 1 addition & 0 deletions tools/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/ossf/scorecard/tools
go 1.17

require (
github.com/golang/mock v1.6.0
github.com/golangci/golangci-lint v1.42.1
github.com/google/addlicense v1.0.0
github.com/google/ko v0.9.3
Expand Down
1 change: 1 addition & 0 deletions tools/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down
4 changes: 3 additions & 1 deletion tools/tools.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build tools
// +build tools

// Copyright 2020 Security Scorecard Authors
Expand All @@ -17,10 +18,11 @@
package main

import (
_ "github.com/golang/mock/mockgen"
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
_ "github.com/google/addlicense"
_ "github.com/google/ko"
_ "github.com/naveensrinivasan/stunning-tribble"
_ "github.com/onsi/ginkgo/ginkgo"
_ "google.golang.org/protobuf/cmd/protoc-gen-go"
_ "github.com/google/ko"
)

0 comments on commit 51de6b6

Please sign in to comment.