Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scottluskcis/issue18 #19

Merged
merged 6 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 16 additions & 33 deletions src/data/copilot-associations-data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { listRepoCollaborators } from "../restapi/collaborators";
import { listCopilotSeats } from "../restapi/copilot";
import { getAuditLogForActor } from "../restapi/organizations";
import { listRepoContributors } from "../restapi/repositories";
Expand Down Expand Up @@ -32,13 +31,13 @@ export async function generateCopilotAssociationsData({
const copilot_seats = await fetchCopilotSeats(org, per_page);

// because the audit may not reflect that a copilot user is in a team, we need to get all teams and their members
await fetchOrgTeamsMembers(org, per_page, teams);
await fetchOrgTeamsMembers(org, per_page, teams, copilot_seats);

// for every copilot user, get their active areas (repos and teams) by looking at the audit log
// NOTE: there is rate limiting with audit log and its not intended to be used in this way constantly
// may need to reconsider other otpions for future
for (const seat of copilot_seats) {
await processActiveAreas(org, seat.assignee, time_period, per_page, teams, repositories);
await processActiveAreas(org, seat.assignee, time_period, per_page, repositories);
}

return { copilot_seats, teams, repositories };
Expand All @@ -57,72 +56,56 @@ async function fetchCopilotSeats(org: string, per_page: number): Promise<Copilot
return copilot_seats;
}

async function processActiveAreas(org: string, seat_assignee: string, time_period: TimePeriodType, per_page: number, teams: { [team: string]: TeamInfo }, repositories: { [repo: string]: Repository }) {
async function processActiveAreas(org: string, seat_assignee: string, time_period: TimePeriodType, per_page: number, repositories: { [repo: string]: Repository }) {
const active_areas_iterator = getUserActiveAreas({
org,
actor: seat_assignee,
include: "all",
include: "git", // for now only include git activity
time_period: time_period,
per_page: per_page,
});
});

for await (const active_area of active_areas_iterator) {
if (active_area.team) {
const [org_name, team_name] = active_area.team.split("/");
await processTeams(org, team_name, seat_assignee, per_page, teams);
}
for await (const active_area of active_areas_iterator) {
if (active_area.repository) {
await processRepositories(active_area.repository, seat_assignee, per_page, repositories);
}
}
}

async function processTeams(org: string, team_name: string, seat_assignee: string | undefined, per_page: number, teams: { [team: string]: TeamInfo }) {
async function processTeams(org: string, team_name: string, per_page: number, teams: { [team: string]: TeamInfo }, org_copilot_seats: CopilotSeatAssignee[]) {
let has_loaded_members = false;
if(!teams[team_name]) {
teams[team_name] = { team_name: team_name, members: [], copilot_users: [] };
} else {
has_loaded_members = teams[team_name].members.length > 0;
}

if (seat_assignee && !teams[team_name].copilot_users.includes(seat_assignee)) {
logger.debug(`Found team ${team_name} for user ${seat_assignee}`);
teams[team_name].copilot_users.push(seat_assignee);
}

if (!has_loaded_members) {
const team_slug = teams[team_name].team_name;
for await (const member of listTeamMembers({ org, team_slug, per_page })) {
const member_name = member.login;
teams[team_name].members.push(member_name);
logger.debug(`Found team member ${member_name} for team ${team_name}`);

if (org_copilot_seats.some(seat => seat.assignee === member_name) && !teams[team_name].copilot_users.includes(member_name)) {
teams[team_name].copilot_users.push(member_name);
logger.debug(`Added copilot user ${member_name} to team ${team_name}`);
}
}
}
}

async function processRepositories(repository_owner_name: string, seat_assignee: string, per_page: number, repositories: { [repo: string]: Repository }) {
const collaborator_affiliation = 'direct';
if (repositories[repository_owner_name]) {
if (!repositories[repository_owner_name].associated_copilot_users.includes(seat_assignee)) {
logger.trace(`Found repo ${repository_owner_name} for user ${seat_assignee}`);
repositories[repository_owner_name].associated_copilot_users.push(seat_assignee);
}
} else {
const [owner, repo_name] = repository_owner_name.split("/");
repositories[repository_owner_name] = { repo_owner: owner, repo_name: repo_name, collaborators: [], collaborator_affiliation: collaborator_affiliation, contributors: [], associated_copilot_users: [seat_assignee] };
repositories[repository_owner_name] = { repo_owner: owner, repo_name: repo_name, contributors: [], associated_copilot_users: [seat_assignee] };
logger.trace(`Found repo ${owner}/${repo_name} for user ${seat_assignee}`);

// collaborators
let collaborator_count: number = 0;
for await (const collaborator of listRepoCollaborators({ owner, repo: repo_name, per_page, affiliation: collaborator_affiliation })) {
const collaborator_name = collaborator.login;
logger.trace(`Found collaborator ${collaborator_name} for repo ${repo_name}`);
repositories[repository_owner_name].collaborators.push(collaborator_name);

collaborator_count++;
}
logger.info(`Found ${collaborator_count} collaborators for repo ${repo_name}`);

// contributors
let contributor_count: number = 0;
for await (const contributor of listRepoContributors({ owner, repo: repo_name, per_page })) {
Expand All @@ -137,10 +120,10 @@ async function processRepositories(repository_owner_name: string, seat_assignee:
}
}

async function fetchOrgTeamsMembers(org: string, per_page: number, teams: { [team: string]: TeamInfo }) {
async function fetchOrgTeamsMembers(org: string, per_page: number, teams: { [team: string]: TeamInfo }, org_copilot_seats: CopilotSeatAssignee[]) {
let team_count: number = 0;
for await (const team of listTeams({ org, per_page })) {
processTeams(org, team.slug, undefined, per_page, teams);
processTeams(org, team.slug, per_page, teams, org_copilot_seats);
team_count++;
}
logger.info(`Found ${team_count} total teams in org ${org}`);
Expand Down Expand Up @@ -232,7 +215,7 @@ async function* getUserActiveAreas({
if (!actor) {
continue;
}

const team = audit_log_entry.team;
const repository = audit_log_entry.repository || audit_log_entry.repo;
const timestamp = timestampToDate(audit_log_entry["@timestamp"]);
Expand Down
25 changes: 6 additions & 19 deletions src/report/copilot-associations-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ export interface CopilotAssociationsData {
repositories: {
[repo_name: string]: {
repo_owner: string;
repo_name: string;
collaborators: string[];
collaborator_affiliation: string;
repo_name: string;
contributors: string[];
associated_copilot_users: string[];
};
Expand Down Expand Up @@ -107,8 +105,9 @@ function getTeamAssociations(data: CopilotAssociationsData): CopilotAssociation[
if (copilot_users.length === 0) {
results.push(createAssociation(member, team.team_name, false, "Unknown", "team"));
}
} else {
results.push(createAssociation(member, team.team_name, true, "Self", "team"));
} else {
logger.warn(`Found copilot user ${member} in team ${team.team_name}, ignoring for report...`);
//results.push(createAssociation(member, team.team_name, true, "Self", "team"));
}
}
}
Expand All @@ -123,19 +122,6 @@ function getRepositoryAssociations(data: CopilotAssociationsData): CopilotAssoci
const repo = data.repositories[repo_name];
const copilot_users = repo.associated_copilot_users;

for (const collaborator of repo.collaborators) {
if (!copilot_users.includes(collaborator)) {
for (const copilot_user of copilot_users) {
results.push(createAssociation(collaborator, repo.repo_name, false, copilot_user, "repository"));
}
if (copilot_users.length === 0) {
results.push(createAssociation(collaborator, repo.repo_name, false, "Unknown", "repository"));
}
} else {
results.push(createAssociation(collaborator, repo.repo_name, true, "Self", "repository"));
}
}

for (const contributor of repo.contributors) {
if (!copilot_users.includes(contributor)) {
for (const copilot_user of copilot_users) {
Expand All @@ -145,7 +131,8 @@ function getRepositoryAssociations(data: CopilotAssociationsData): CopilotAssoci
results.push(createAssociation(contributor, repo.repo_name, false, "Unknown", "repository"));
}
} else {
results.push(createAssociation(contributor, repo.repo_name, true, "Self", "repository"));
logger.warn(`Found copilot user ${contributor} in repo ${repo.repo_name}, ignoring for report...`);
//results.push(createAssociation(contributor, repo.repo_name, true, "Self", "repository"));
}
}
}
Expand Down
4 changes: 1 addition & 3 deletions src/shared/shared-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ export interface UserSummary {

export type Repository = {
repo_owner: string;
repo_name: string;
collaborators: string[];
collaborator_affiliation: string;
repo_name: string;
contributors: string[];
associated_copilot_users: string[];
};
Expand Down
Loading