Skip to content

Commit

Permalink
sparkles: add ListProgrammingLanguages for Azure DevOps
Browse files Browse the repository at this point in the history
  • Loading branch information
JamieMagee committed Dec 4, 2024
1 parent 86f46b1 commit 889bebc
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 6 deletions.
14 changes: 13 additions & 1 deletion clients/azuredevopsrepo/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/microsoft/azure-devops-go-api/azuredevops/v7"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/git"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/projectanalysis"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/search"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/workitemtracking"

Expand All @@ -46,6 +47,7 @@ type Client struct {
audit *auditHandler
branches *branchesHandler
commits *commitsHandler
languages *languagesHandler
search *searchHandler
workItems *workItemsHandler
zip *zipHandler
Expand Down Expand Up @@ -93,6 +95,8 @@ func (c *Client) InitRepo(inputRepo clients.Repo, commitSHA string, commitDepth

c.commits.init(c.ctx, c.repourl, c.commitDepth)

c.languages.init(c.ctx, c.repourl)

c.search.init(c.ctx, c.repourl)

c.workItems.init(c.ctx, c.repourl)
Expand Down Expand Up @@ -192,7 +196,7 @@ func (c *Client) ListWebhooks() ([]clients.Webhook, error) {
}

func (c *Client) ListProgrammingLanguages() ([]clients.Language, error) {
return nil, clients.ErrUnsupportedFeature
return c.languages.listProgrammingLanguages()
}

func (c *Client) Search(request clients.SearchRequest) (clients.SearchResponse, error) {
Expand Down Expand Up @@ -229,6 +233,11 @@ func CreateAzureDevOpsClientWithToken(ctx context.Context, token string, repo cl
return nil, fmt.Errorf("could not create azure devops git client with error: %w", err)
}

projectAnalysisClient, err := projectanalysis.NewClient(ctx, connection)
if err != nil {
return nil, fmt.Errorf("could not create azure devops project analysis client with error: %w", err)
}

searchClient, err := search.NewClient(ctx, connection)
if err != nil {
return nil, fmt.Errorf("could not create azure devops search client with error: %w", err)
Expand All @@ -251,6 +260,9 @@ func CreateAzureDevOpsClientWithToken(ctx context.Context, token string, repo cl
commits: &commitsHandler{
gitClient: gitClient,
},
languages: &languagesHandler{
projectAnalysisClient: projectAnalysisClient,
},
search: &searchHandler{
searchClient: searchClient,
},
Expand Down
96 changes: 96 additions & 0 deletions clients/azuredevopsrepo/languages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright 2024 OpenSSF 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 azuredevopsrepo

import (
"context"
"log"
"sync"

"github.com/microsoft/azure-devops-go-api/azuredevops/v7/projectanalysis"

"github.com/ossf/scorecard/v5/clients"
)

type languagesHandler struct {
ctx context.Context
once *sync.Once
repourl *Repo
projectAnalysisClient projectanalysis.Client
projectAnalysis fnGetProjectLanguageAnalytics
errSetup error
languages []clients.Language
}

func (l *languagesHandler) init(ctx context.Context, repourl *Repo) {
l.ctx = ctx
l.once = new(sync.Once)
l.repourl = repourl
l.languages = []clients.Language{}
l.projectAnalysis = l.projectAnalysisClient.GetProjectLanguageAnalytics
}

type (
fnGetProjectLanguageAnalytics func(
ctx context.Context,
args projectanalysis.GetProjectLanguageAnalyticsArgs,
) (*projectanalysis.ProjectLanguageAnalytics, error)
)
func (l *languagesHandler) setup() error {
l.once.Do(func() {
args := projectanalysis.GetProjectLanguageAnalyticsArgs{
Project: &l.repourl.project,
}
res, err := l.projectAnalysis(l.ctx, args)
if err != nil {
l.errSetup = err
return
}

if res.ResultPhase != &projectanalysis.ResultPhaseValues.Full {
log.Println("Project language analytics not ready yet. Results may be incomplete.")
}

for _, repo := range *res.RepositoryLanguageAnalytics {
if repo.Id.String() != l.repourl.id {
continue
}

// TODO: Find the number of lines in the repo and multiply the value of each language by that number.
for _, language := range *repo.LanguageBreakdown {
percentage := 0
if language.LanguagePercentage != nil {
percentage = int(*language.LanguagePercentage)
}
l.languages = append(l.languages,
clients.Language{
Name: clients.LanguageName(*language.Name),
NumLines: percentage,
},
)
}

}

Check failure on line 85 in clients/azuredevopsrepo/languages.go

View workflow job for this annotation

GitHub Actions / check-linter

unnecessary trailing newline (whitespace)
})
return l.errSetup
}

func (l *languagesHandler) listProgrammingLanguages() ([]clients.Language, error) {
if err := l.setup(); err != nil {
return nil, err
}

return l.languages, nil
}
95 changes: 95 additions & 0 deletions clients/azuredevopsrepo/languages_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2024 OpenSSF 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 azuredevopsrepo

import (
"context"
"reflect"
"sync"
"testing"

"github.com/google/uuid"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/projectanalysis"

"github.com/ossf/scorecard/v5/clients"
)

func Test_listProgrammingLanguages(t *testing.T) {
t.Parallel()
tests := []struct {
name string
projectAnalysis func(ctx context.Context, args projectanalysis.GetProjectLanguageAnalyticsArgs) (*projectanalysis.ProjectLanguageAnalytics, error)
want []clients.Language
wantErr bool
}{
{
name: "empty response",
projectAnalysis: func(ctx context.Context, args projectanalysis.GetProjectLanguageAnalyticsArgs) (*projectanalysis.ProjectLanguageAnalytics, error) {
return &projectanalysis.ProjectLanguageAnalytics{
RepositoryLanguageAnalytics: &[]projectanalysis.RepositoryLanguageAnalytics{},
}, nil
},
want: []clients.Language(nil),
wantErr: false,
},
{
name: "single response",
projectAnalysis: func(ctx context.Context, args projectanalysis.GetProjectLanguageAnalyticsArgs) (*projectanalysis.ProjectLanguageAnalytics, error) {
return &projectanalysis.ProjectLanguageAnalytics{
RepositoryLanguageAnalytics: &[]projectanalysis.RepositoryLanguageAnalytics{
{
Id: toPtr(uuid.Nil),
LanguageBreakdown: &[]projectanalysis.LanguageStatistics{
{
Name: toPtr("test"),
LanguagePercentage: toPtr(float64(100)),
},
},
},
},
}, nil
},
want: []clients.Language{
{
Name: "test",
NumLines: 100,
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
l := &languagesHandler{
once: new(sync.Once),
ctx: context.Background(),
repourl: &Repo{
id: uuid.Nil.String(),
project: "project",
},
projectAnalysis: tt.projectAnalysis,
}
got, err := l.listProgrammingLanguages()
if (err != nil) != tt.wantErr {
t.Errorf("listProgrammingLanguages() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("listProgrammingLanguages() got = %v, want %v", got, tt.want)
}
})
}
}
8 changes: 3 additions & 5 deletions clients/azuredevopsrepo/zip.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,9 @@ func (z *zipHandler) extractZip() error {
if !strings.HasPrefix(filepath.Clean(filenamepath), destinationPrefix) {
return errInvalidFilePath
}
if file.FileInfo().IsDir() {
if err := os.MkdirAll(filenamepath, 0o755); err != nil {
return fmt.Errorf("error during os.MkdirAll: %w", err)
}
continue

if err := os.MkdirAll(filepath.Dir(filenamepath), 0o755); err != nil {
return fmt.Errorf("error during os.MkdirAll: %w", err)
}

outFile, err := os.OpenFile(filenamepath, os.O_CREATE|os.O_WRONLY, 0o644)
Expand Down

0 comments on commit 889bebc

Please sign in to comment.