Skip to content

Commit 7ab1f3a

Browse files
committed
Introduce a way to process API responses into variant analyses
This receives an API response and builds a VariantAnalysis from the fields.
1 parent 17ed18a commit 7ab1f3a

2 files changed

Lines changed: 388 additions & 0 deletions

File tree

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import {
2+
VariantAnalysis as ApiVariantAnalysis,
3+
VariantAnalysisScannedRepository as ApiVariantAnalysisScannedRepository,
4+
VariantAnalysisSkippedRepositories as ApiVariantAnalysisSkippedRepositories,
5+
VariantAnalysisRepoStatus as ApiVariantAnalysisRepoStatus,
6+
VariantAnalysisFailureReason as ApiVariantAnalysisFailureReason,
7+
VariantAnalysisStatus as ApiVariantAnalysisStatus,
8+
VariantAnalysisSkippedRepositoryGroup as ApiVariantAnalysisSkippedRepositoryGroup,
9+
VariantAnalysisNotFoundRepositoryGroup as ApiVariantAnalysisNotFoundRepositoryGroup
10+
} from './gh-api/variant-analysis';
11+
import {
12+
VariantAnalysis,
13+
VariantAnalysisFailureReason,
14+
VariantAnalysisScannedRepository,
15+
VariantAnalysisSkippedRepositories,
16+
VariantAnalysisStatus,
17+
VariantAnalysisRepoStatus,
18+
VariantAnalysisSubmission,
19+
VariantAnalysisSkippedRepositoryGroup
20+
} from './shared/variant-analysis';
21+
22+
export function processVariantAnalysis(
23+
submission: VariantAnalysisSubmission,
24+
response: ApiVariantAnalysis
25+
): VariantAnalysis {
26+
27+
let scannedRepos: VariantAnalysisScannedRepository[] = [];
28+
let skippedRepos: VariantAnalysisSkippedRepositories = {};
29+
30+
if (response.scanned_repositories) {
31+
scannedRepos = processScannedRepositories(response.scanned_repositories as ApiVariantAnalysisScannedRepository[]);
32+
}
33+
34+
if (response.skipped_repositories) {
35+
skippedRepos = processSkippedRepositories(response.skipped_repositories as ApiVariantAnalysisSkippedRepositories);
36+
}
37+
38+
const variantAnalysis: VariantAnalysis = {
39+
id: response.id,
40+
controllerRepoId: response.controller_repo.id,
41+
query: {
42+
name: submission.query.name,
43+
filePath: submission.query.filePath,
44+
language: submission.query.language
45+
},
46+
databases: submission.databases,
47+
status: processApiStatus(response.status),
48+
actionsWorkflowRunId: response.actions_workflow_run_id,
49+
scannedRepos: scannedRepos,
50+
skippedRepos: skippedRepos
51+
};
52+
53+
if (response.failure_reason) {
54+
variantAnalysis.failureReason = response.failure_reason as VariantAnalysisFailureReason;
55+
}
56+
57+
return variantAnalysis;
58+
}
59+
60+
function processScannedRepositories(
61+
scannedRepos: ApiVariantAnalysisScannedRepository[]
62+
): VariantAnalysisScannedRepository[] {
63+
64+
const result: VariantAnalysisScannedRepository[] = [];
65+
66+
scannedRepos.forEach(function(scannedRepo) {
67+
const parsedRepo: VariantAnalysisScannedRepository = {
68+
repository: {
69+
id: scannedRepo.repository.id,
70+
fullName: scannedRepo.repository.full_name,
71+
private: scannedRepo.repository.private,
72+
},
73+
analysisStatus: processApiRepoStatus(scannedRepo.analysis_status),
74+
resultCount: scannedRepo.result_count,
75+
artifactSizeInBytes: scannedRepo.artifact_size_in_bytes,
76+
failureMessage: scannedRepo.failure_message
77+
};
78+
79+
result.push(parsedRepo);
80+
});
81+
82+
return result;
83+
}
84+
85+
function processSkippedRepositories(
86+
skippedRepos: ApiVariantAnalysisSkippedRepositories
87+
): VariantAnalysisSkippedRepositories {
88+
89+
return {
90+
accessMismatchRepos: processRepoGroup(skippedRepos.access_mismatch_repos),
91+
notFoundRepos: processNotFoundRepoGroup(skippedRepos.not_found_repo_nwos),
92+
noCodeqlDbRepos: processRepoGroup(skippedRepos.no_codeql_db_repos),
93+
overLimitRepos: processRepoGroup(skippedRepos.over_limit_repos)
94+
};
95+
}
96+
97+
function processRepoGroup(repoGroup: ApiVariantAnalysisSkippedRepositoryGroup): VariantAnalysisSkippedRepositoryGroup {
98+
const repos = repoGroup.repositories.map(repo => {
99+
return {
100+
id: repo.id,
101+
fullName: repo.full_name
102+
};
103+
});
104+
105+
return {
106+
repositoryCount: repoGroup.repository_count,
107+
repositories: repos
108+
};
109+
}
110+
111+
function processNotFoundRepoGroup(repoGroup: ApiVariantAnalysisNotFoundRepositoryGroup): VariantAnalysisSkippedRepositoryGroup {
112+
const repo_nwos = repoGroup.repository_nwos.map(nwo => {
113+
return {
114+
fullName: nwo
115+
};
116+
});
117+
118+
return {
119+
repositoryCount: repoGroup.repository_count,
120+
repositories: repo_nwos
121+
};
122+
}
123+
124+
function processApiRepoStatus(analysisStatus: ApiVariantAnalysisRepoStatus): VariantAnalysisRepoStatus {
125+
switch (analysisStatus) {
126+
case 'pending':
127+
return VariantAnalysisRepoStatus.Pending;
128+
case 'in_progress':
129+
return VariantAnalysisRepoStatus.InProgress;
130+
case 'succeeded':
131+
return VariantAnalysisRepoStatus.Succeeded;
132+
case 'failed':
133+
return VariantAnalysisRepoStatus.Failed;
134+
case 'canceled':
135+
return VariantAnalysisRepoStatus.Canceled;
136+
case 'timed_out':
137+
return VariantAnalysisRepoStatus.TimedOut;
138+
}
139+
}
140+
141+
function processApiStatus(status: ApiVariantAnalysisStatus): VariantAnalysisStatus {
142+
switch (status) {
143+
case 'in_progress':
144+
return VariantAnalysisStatus.InProgress;
145+
case 'completed':
146+
return VariantAnalysisStatus.Succeeded;
147+
}
148+
}
149+
150+
export function processFailureReason(failureReason: ApiVariantAnalysisFailureReason): VariantAnalysisFailureReason {
151+
switch (failureReason) {
152+
case 'no_repos_queried':
153+
return VariantAnalysisFailureReason.NoReposQueried;
154+
case 'internal_error':
155+
return VariantAnalysisFailureReason.InternalError;
156+
}
157+
}
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import { faker } from '@faker-js/faker';
2+
import { expect } from 'chai';
3+
import {
4+
VariantAnalysis as VariantAnalysisApiResponse,
5+
VariantAnalysisRepoStatus as ApiVariantAnalysisRepoStatus,
6+
VariantAnalysisScannedRepository as ApiVariantAnalysisScannedRepository,
7+
VariantAnalysisSkippedRepositories as ApiVariantAnalysisSkippedRepositories,
8+
VariantAnalysisSkippedRepositoryGroup as ApiVariantAnalysisSkippedRepositoryGroup,
9+
VariantAnalysisNotFoundRepositoryGroup as ApiVariantAnalysisNotFoundRepositoryGroup
10+
} from '../../../remote-queries/gh-api/variant-analysis';
11+
import {
12+
VariantAnalysisSubmission,
13+
VariantAnalysisQueryLanguage,
14+
VariantAnalysisSkippedRepositories,
15+
VariantAnalysisSkippedRepositoryGroup
16+
} from '../../../remote-queries/shared/variant-analysis';
17+
import { processVariantAnalysis } from '../../../remote-queries/variant-analysis-processor';
18+
19+
describe('Variant Analysis processor', function() {
20+
let mockApiResponse: VariantAnalysisApiResponse;
21+
let mockSubmission: VariantAnalysisSubmission;
22+
let scannedRepo1: ApiVariantAnalysisScannedRepository;
23+
let scannedRepo2: ApiVariantAnalysisScannedRepository;
24+
let scannedRepo3: ApiVariantAnalysisScannedRepository;
25+
let skippedRepos: ApiVariantAnalysisSkippedRepositories;
26+
27+
beforeEach(() => {
28+
scannedRepo1 = createMockScannedRepo('mona1', false, 'succeeded');
29+
scannedRepo2 = createMockScannedRepo('mona2', false, 'pending');
30+
scannedRepo3 = createMockScannedRepo('mona3', false, 'in_progress');
31+
skippedRepos = createMockSkippedRepos();
32+
33+
mockApiResponse = createMockApiResponse();
34+
mockSubmission = createMockSubmission();
35+
});
36+
37+
it('should process an API response and return a variant analysis', () => {
38+
const result = processVariantAnalysis(mockSubmission, mockApiResponse);
39+
40+
expect(result).to.eql({
41+
'id': 123,
42+
'controllerRepoId': 456,
43+
'query': {
44+
'filePath': 'query-file-path',
45+
'language': 'javascript',
46+
'name': 'query-name',
47+
},
48+
'databases': {
49+
'repositories': ['1', '2', '3'],
50+
'repositoryLists': ['top10', 'top100'],
51+
'repositoryOwners': ['mona', 'lisa']
52+
},
53+
'status': 'succeeded',
54+
'actionsWorkflowRunId': 456,
55+
'failureReason': 'internal_error',
56+
'scannedRepos': [
57+
{
58+
'analysisStatus': 'succeeded',
59+
'artifactSizeInBytes': scannedRepo1.artifact_size_in_bytes,
60+
'failureMessage': '',
61+
'repository': {
62+
'fullName': scannedRepo1.repository.full_name,
63+
'id': scannedRepo1.repository.id,
64+
'private': scannedRepo1.repository.private,
65+
},
66+
'resultCount': scannedRepo1.result_count
67+
},
68+
{
69+
'analysisStatus': 'pending',
70+
'artifactSizeInBytes': scannedRepo2.artifact_size_in_bytes,
71+
'failureMessage': '',
72+
'repository': {
73+
'fullName': scannedRepo2.repository.full_name,
74+
'id': scannedRepo2.repository.id,
75+
'private': scannedRepo2.repository.private,
76+
},
77+
'resultCount': scannedRepo2.result_count
78+
},
79+
{
80+
'analysisStatus': 'inProgress',
81+
'artifactSizeInBytes': scannedRepo3.artifact_size_in_bytes,
82+
'failureMessage': '',
83+
'repository': {
84+
'fullName': scannedRepo3.repository.full_name,
85+
'id': scannedRepo3.repository.id,
86+
'private': scannedRepo3.repository.private,
87+
},
88+
'resultCount': scannedRepo3.result_count
89+
}
90+
],
91+
'skippedRepos': transformSkippedRepos(skippedRepos)
92+
});
93+
});
94+
95+
function createMockApiResponse(): VariantAnalysisApiResponse {
96+
const variantAnalysis: VariantAnalysisApiResponse = {
97+
id: 123,
98+
controller_repo: {
99+
id: 456,
100+
name: 'pickles',
101+
full_name: 'github/pickles',
102+
private: false,
103+
},
104+
actor_id: 123,
105+
query_language: 'javascript',
106+
query_pack_url: 'https://example.com/foo',
107+
status: 'in_progress',
108+
actions_workflow_run_id: 456,
109+
failure_reason: 'internal_error',
110+
scanned_repositories: [scannedRepo1, scannedRepo2, scannedRepo3],
111+
skipped_repositories: skippedRepos
112+
};
113+
114+
return variantAnalysis;
115+
}
116+
117+
function createMockScannedRepo(
118+
name: string,
119+
isPrivate: boolean,
120+
analysisStatus: ApiVariantAnalysisRepoStatus,
121+
): ApiVariantAnalysisScannedRepository {
122+
return {
123+
repository: {
124+
id: faker.datatype.number(),
125+
name: name,
126+
full_name: 'github/' + name,
127+
private: isPrivate,
128+
},
129+
analysis_status: analysisStatus,
130+
result_count: faker.datatype.number(),
131+
artifact_size_in_bytes: faker.datatype.number(),
132+
failure_message: ''
133+
};
134+
}
135+
136+
function createMockSubmission(): VariantAnalysisSubmission {
137+
return {
138+
startTime: 1234,
139+
controllerRepoId: 5678,
140+
actionRepoRef: 'repo-ref',
141+
query: {
142+
name: 'query-name',
143+
filePath: 'query-file-path',
144+
language: VariantAnalysisQueryLanguage.Javascript,
145+
pack: 'base64-encoded-string',
146+
},
147+
databases: {
148+
repositories: ['1', '2', '3'],
149+
repositoryLists: ['top10', 'top100'],
150+
repositoryOwners: ['mona', 'lisa'],
151+
}
152+
};
153+
}
154+
155+
function createMockSkippedRepos(): ApiVariantAnalysisSkippedRepositories {
156+
return {
157+
access_mismatch_repos: createMockSkippedRepoGroup(),
158+
no_codeql_db_repos: createMockSkippedRepoGroup(),
159+
not_found_repo_nwos: createMockNotFoundSkippedRepoGroup(),
160+
over_limit_repos: createMockSkippedRepoGroup()
161+
};
162+
}
163+
164+
function createMockSkippedRepoGroup(): ApiVariantAnalysisSkippedRepositoryGroup {
165+
return {
166+
repository_count: 2,
167+
repositories: [
168+
{
169+
id: faker.datatype.number(),
170+
name: faker.random.word(),
171+
full_name: 'github/' + faker.random.word(),
172+
private: true
173+
},
174+
{
175+
id: faker.datatype.number(),
176+
name: faker.random.word(),
177+
full_name: 'github/' + faker.random.word(),
178+
private: false
179+
}
180+
]
181+
};
182+
}
183+
184+
function createMockNotFoundSkippedRepoGroup(): ApiVariantAnalysisNotFoundRepositoryGroup {
185+
const repoName1 = 'github' + faker.random.word();
186+
const repoName2 = 'github' + faker.random.word();
187+
188+
return {
189+
repository_count: 2,
190+
repository_nwos: [repoName1, repoName2]
191+
};
192+
}
193+
194+
function transformSkippedRepos(
195+
skippedRepos: ApiVariantAnalysisSkippedRepositories
196+
): VariantAnalysisSkippedRepositories {
197+
return {
198+
accessMismatchRepos: transformSkippedRepoGroup(skippedRepos.access_mismatch_repos),
199+
noCodeqlDbRepos: transformSkippedRepoGroup(skippedRepos.no_codeql_db_repos),
200+
notFoundRepos: transformNotFoundRepoGroup(skippedRepos.not_found_repo_nwos),
201+
overLimitRepos: transformSkippedRepoGroup(skippedRepos.over_limit_repos)
202+
};
203+
}
204+
});
205+
206+
function transformSkippedRepoGroup(repoGroup: ApiVariantAnalysisSkippedRepositoryGroup): VariantAnalysisSkippedRepositoryGroup {
207+
const repos = repoGroup.repositories.map(repo => {
208+
return {
209+
id: repo.id,
210+
fullName: repo.full_name
211+
};
212+
});
213+
214+
return {
215+
repositoryCount: repoGroup.repository_count,
216+
repositories: repos
217+
};
218+
}
219+
220+
function transformNotFoundRepoGroup(repoGroup: ApiVariantAnalysisNotFoundRepositoryGroup): VariantAnalysisSkippedRepositoryGroup {
221+
const repos = repoGroup.repository_nwos.map(nwo => {
222+
return {
223+
fullName: nwo
224+
};
225+
});
226+
227+
return {
228+
repositoryCount: repoGroup.repository_count,
229+
repositories: repos
230+
};
231+
}

0 commit comments

Comments
 (0)