Skip to content

Commit

Permalink
Merge pull request #7 from snyk-tech-services/fix/azure-devops-emptyP…
Browse files Browse the repository at this point in the history
…roject

fix: added support for empty projects array, used bottleneck and added tests
  • Loading branch information
IlanTSnyk authored Jun 28, 2021
2 parents 367defc + a15ac39 commit 0788fd0
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 28 deletions.
2 changes: 1 addition & 1 deletion src/cmds/azure-devops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const builder = {
desc: 'Your Org name in Azure Devops e.g. https://dev.azure.com/{OrgName}',
},
projectKeys: {
required: true,
required: false,
default: undefined,
desc: '[Optional] Azure Devops project key/name to count contributors for',
},
Expand Down
44 changes: 41 additions & 3 deletions src/lib/azure-devops/azure-devops-contributors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
Contributor,
ContributorMap,
} from '../types';
import { Commits, Repo } from './types';
import { getRepoCommits, getReposPerProjects } from './utils';
import { Commits, Repo, Project } from './types';
import { getRepoCommits, getReposPerProjects, getProjects } from './utils';

import * as debugLib from 'debug';
const azureDefaultUrl = 'https://dev.azure.com/';
Expand All @@ -19,7 +19,7 @@ export const fetchAzureDevopsContributors = async (
const contributorsMap = new Map<Username, Contributor>();
try {
let repoList: Repo[] = [];

let projectList: Project[] = [];
if (
azureInfo.repo &&
(!azureInfo.projectKeys || azureInfo.projectKeys.length > 1)
Expand All @@ -36,6 +36,19 @@ export const fetchAzureDevopsContributors = async (
project: { key: azureInfo.projectKeys[0] },
});
}
} else if (!azureInfo.projectKeys) {
azureInfo.projectKeys = [];
projectList = projectList.concat(
await fetchAzureProjects(
azureDefaultUrl,
azureInfo.OrgName,
azureInfo.token,
),
);
for (let i = 0; i < projectList.length; i++) {
azureInfo.projectKeys.push(projectList[i].name);
}
repoList = repoList.concat(await fetchAzureReposForProjects(azureInfo));
} else {
// Otherwise retrieve all repos (for given projects or all repos)
repoList = repoList.concat(await fetchAzureReposForProjects(azureInfo));
Expand Down Expand Up @@ -158,3 +171,28 @@ export const fetchAzureReposForProjects = async (
}
return repoList;
};

export const fetchAzureProjects = async (
azureDefaultUrl: string,
OrgName: string,
token: string,
): Promise<Project[]> => {
const projectList: Project[] = [];
try {
const projects = await getProjects(azureDefaultUrl, OrgName, token);
const result = await projects.text();
const parsedResponse = JSON.parse(result).value;
parsedResponse.map((project: { name: string; id: string }) => {
const { name, id } = project;
if (name && id) {
projectList.push({ id: project.id, name: project.name });
}
});
} catch (err) {
debug('Failed to retrieve project list from Azure Devops.\n' + err);
console.log(
'Failed to retrieve project list from Azure Devops. Try running with `DEBUG=snyk* snyk-contributor`',
);
}
return projectList;
};
5 changes: 5 additions & 0 deletions src/lib/azure-devops/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ export interface Repo {
};
public?: boolean;
}

export interface Project {
name: string;
id: string;
}
66 changes: 44 additions & 22 deletions src/lib/azure-devops/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,21 @@ export const getRepoCommits = async (
threeMonthsDate: string,
): Promise<Response> => {
debug('Fetching commints for ' + repo);
const data = await fetch(
url +
'/' +
project +
'/_apis/git/repositories/' +
repo +
'/commits?$top=1000&searchCriteria.fromDate=' +
threeMonthsDate +
'&api-version=4.1',
{
method: 'GET',
headers: { Authorization: 'Basic ' + base64.encode(':' + token) },
},
const data = await limiter.schedule(() =>
fetch(
url +
'/' +
project +
'/_apis/git/repositories/' +
repo +
'/commits?$top=1000000&searchCriteria.fromDate=' +
threeMonthsDate +
'&api-version=4.1',
{
method: 'GET',
headers: { Authorization: 'Basic ' + base64.encode(':' + token) },
},
),
);
return data;
};
Expand All @@ -52,15 +54,35 @@ export const getReposPerProjects = async (
token: string,
): Promise<Response> => {
debug('Fetching repos for ' + project);
const data = await fetch(
url +
'/' +
project +
'/_apis/git/repositories?$top=1000000&api-version=4.1',
{
method: 'GET',
headers: { Authorization: 'Basic ' + base64.encode(':' + token) },
},
const data = await limiter.schedule(() =>
fetch(
url +
'/' +
project +
'/_apis/git/repositories?$top=1000000&api-version=4.1',
{
method: 'GET',
headers: { Authorization: 'Basic ' + base64.encode(':' + token) },
},
),
);
return data;
};

export const getProjects = async (
url: string,
OrgName: string,
token: string,
): Promise<Response> => {
debug('Fetching projects');
const data = await limiter.schedule(() =>
fetch(
url + '/' + OrgName + '/_apis/projects?$top=1000000&api-version=4.1',
{
method: 'GET',
headers: { Authorization: 'Basic ' + base64.encode(':' + token) },
},
),
);
return data;
};
95 changes: 95 additions & 0 deletions test/fixtures/azure-devops/org-projects.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"count": 10,
"value": [
{
"id": "371efd3e-3e86-4d33-846d-e5e46397dd91",
"name": "Test68",
"url": "https://dev.azure.com/ilantorbaty/_apis/projects/371efd3e-3e86-4d33-846d-e5e46397dd91",
"state": "wellFormed",
"revision": 555,
"visibility": "private",
"lastUpdateTime": "2021-04-05T18:20:39.37Z"
},
{
"id": "5bdff632-3ff7-4378-9d25-1c6355885b0f",
"name": "Test27",
"url": "https://dev.azure.com/ilantorbaty/_apis/projects/5bdff632-3ff7-4378-9d25-1c6355885b0f",
"state": "wellFormed",
"revision": 227,
"visibility": "private",
"lastUpdateTime": "2021-04-05T18:07:53.72Z"
},
{
"id": "9bf8aab6-8ae1-48d3-a19f-2541fb325d7d",
"name": "Test103",
"url": "https://dev.azure.com/ilantorbaty/_apis/projects/9bf8aab6-8ae1-48d3-a19f-2541fb325d7d",
"state": "wellFormed",
"revision": 835,
"visibility": "private",
"lastUpdateTime": "2021-04-06T05:46:43.277Z"
},
{
"id": "fec060ec-a6a0-452f-b650-c4fbee4499a9",
"name": "Test102",
"url": "https://dev.azure.com/ilantorbaty/_apis/projects/fec060ec-a6a0-452f-b650-c4fbee4499a9",
"state": "wellFormed",
"revision": 827,
"visibility": "private",
"lastUpdateTime": "2021-04-06T05:46:31Z"
},
{
"id": "06fae37f-a730-4265-9908-fc9ac3fad9a5",
"name": "Test117",
"url": "https://dev.azure.com/ilantorbaty/_apis/projects/06fae37f-a730-4265-9908-fc9ac3fad9a5",
"state": "wellFormed",
"revision": 947,
"visibility": "private",
"lastUpdateTime": "2021-04-06T05:49:37.6Z"
},
{
"id": "369cd140-6b15-4937-adf6-b251c57ed6d5",
"name": "Test72",
"url": "https://dev.azure.com/ilantorbaty/_apis/projects/369cd140-6b15-4937-adf6-b251c57ed6d5",
"state": "wellFormed",
"revision": 587,
"visibility": "private",
"lastUpdateTime": "2021-04-05T18:24:44.787Z"
},
{
"id": "fc1f134d-02f8-4575-96a8-9c31954234c2",
"name": "Test97",
"url": "https://dev.azure.com/ilantorbaty/_apis/projects/fc1f134d-02f8-4575-96a8-9c31954234c2",
"state": "wellFormed",
"revision": 787,
"visibility": "private",
"lastUpdateTime": "2021-04-05T18:32:33.29Z"
},
{
"id": "e7750c1e-b400-4ccd-9995-836d850c2dfa",
"name": "Test98",
"url": "https://dev.azure.com/ilantorbaty/_apis/projects/e7750c1e-b400-4ccd-9995-836d850c2dfa",
"state": "wellFormed",
"revision": 795,
"visibility": "private",
"lastUpdateTime": "2021-04-05T18:32:46.287Z"
},
{
"id": "2d5eccfc-bb6b-4f89-b77f-36a875b5e4e9",
"name": "Test74",
"url": "https://dev.azure.com/ilantorbaty/_apis/projects/2d5eccfc-bb6b-4f89-b77f-36a875b5e4e9",
"state": "wellFormed",
"revision": 603,
"visibility": "private",
"lastUpdateTime": "2021-04-05T18:26:04.753Z"
},
{
"id": "1e8621c5-2d46-49bb-bd1c-c6914013ed58",
"name": "Test109",
"url": "https://dev.azure.com/ilantorbaty/_apis/projects/1e8621c5-2d46-49bb-bd1c-c6914013ed58",
"state": "wellFormed",
"revision": 883,
"visibility": "private",
"lastUpdateTime": "2021-04-06T05:47:57.977Z"
}
]
}
18 changes: 16 additions & 2 deletions test/lib/azure-devops/azure-devops-contributors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,30 @@ beforeEach(() => {
switch (uri) {
case '/testOrg/testProject/_apis/git/repositories?$top=1000000&api-version=4.1':
return fs.readFileSync(fixturesFolderPath + 'project-repos.json');
case '/testOrg/5ce02f49-7e0e-4aba-9c24-3428111da778/_apis/git/repositories/testRepo1/commits?$top=1000&searchCriteria.fromDate=2000/01/01%2012:00&api-version=4.1':
case '/testOrg/5ce02f49-7e0e-4aba-9c24-3428111da778/_apis/git/repositories/testRepo1/commits?$top=1000000&searchCriteria.fromDate=2000/01/01%2012:00&api-version=4.1':
return fs.readFileSync(fixturesFolderPath + 'testRepo-commits.json');
case '/testOrg/5ce02f49-7e0e-4aba-9c24-3428111da778/_apis/git/repositories/goof.git/commits?$top=1000&searchCriteria.fromDate=2000/01/01%2012:00&api-version=4.1':
case '/testOrg/5ce02f49-7e0e-4aba-9c24-3428111da778/_apis/git/repositories/goof.git/commits?$top=1000000&searchCriteria.fromDate=2000/01/01%2012:00&api-version=4.1':
return fs.readFileSync(fixturesFolderPath + 'goofRepo-commits.json');
case '/testOrg/_apis/projects?$top=1000000&api-version=4.1':
return fs.readFileSync(fixturesFolderPath + 'org-projects.json');
default:
}
});
});

describe('Testing azure-devops interaction', () => {
test('Test fetchAzureProjects', async () => {
const projects = await azureDevops.fetchAzureProjects(
'https://dev.azure.com',
'testOrg',
'123',
);
expect(projects).toHaveLength(10);
expect(projects[0].name).toEqual('Test68');
expect(projects[1].id).toEqual('5bdff632-3ff7-4378-9d25-1c6355885b0f');
expect(projects[0]).not.toEqual(projects[1]);
});

test('Test fetchAzureReposForProjects', async () => {
const azureDevopsInfo: AzureDevopsTarget = {
token: '123',
Expand Down

0 comments on commit 0788fd0

Please sign in to comment.