def callback(args: argparse.Namespace, api: plug.PlatformAPI) -> None: repo_name_to_team: Mapping[str, plug.StudentTeam] = { plug.generate_repo_name(student_team.name, assignment_name): student_team for student_team in args.students for assignment_name in args.assignments } repo_names = list(repo_name_to_team.keys()) if "multi_issues_file" in args and args.multi_issues_file is not None: issues_file = pathlib.Path(args.multi_issues_file).resolve() all_issues = _parse_multi_issues_file(issues_file) else: issues_dir = pathlib.Path(args.issues_dir).resolve() all_issues = _collect_issues(repo_names, issues_dir) issues = _extract_expected_issues(all_issues, repo_names, args.allow_missing) for repo_name, issue in issues: open_issue = args.batch_mode or _ask_for_open(issue, repo_name, args.truncation_length) if open_issue: repo = api.get_repo(repo_name, repo_name_to_team[repo_name].name) api.create_issue(issue.title, issue.body, repo) else: plug.echo("Skipping {}".format(repo_name))
def open_issues_from_hook_results( hook_results: Mapping[str, List[plug.Result]], repos: Iterable[plug.StudentRepo], api: plug.PlatformAPI, ) -> None: """Open all issues from the hook results in the given repos. Issues given in the hook results that do not belong to the repos are ignored, and repos provided without corresponding issues in the hook results have no effect. Args: hook_results: A hook results dictionary. repos: Student repos to open issues in. api: plug.PlatformAPI, """ url_to_repo = {repo.url: repo for repo in repos} for repo_url, repo_data in hook_results["repos"][0].data.items(): if repo_url in url_to_repo and repo_data["issues"]: repo = url_to_repo[repo_url] platform_repo = api.get_repo(repo.name, repo.team.name) for issue_data in repo_data["issues"].values(): issue = api.create_issue(issue_data["title"], issue_data["body"], platform_repo) msg = ( f"Opened issue {repo.name}/#{issue.number}-'{issue.title}'" ) plug.echo(msg)
def _open_issue_by_urls(repo_urls: Iterable[str], issue: plug.Issue, api: plug.PlatformAPI) -> None: """Open issues in the repos designated by the repo_urls. Args: repo_urls: URLs to repos in which to open an issue. issue: An issue to open. api: An implementation of :py:class:`repobee_plug.PlatformAPI` used to interface with the platform (e.g. GitHub or GitLab) instance. """ repos = progresswrappers.get_repos(repo_urls, api) for repo in repos: issue = api.create_issue(issue.title, issue.body, repo) msg = f"Opened issue {repo.name}/#{issue.number}-'{issue.title}'" repos.write(msg) # type: ignore plug.log.info(msg)
def open_issue( issue: plug.Issue, assignment_names: Iterable[str], teams: Iterable[plug.StudentTeam], api: plug.PlatformAPI, ) -> None: """Open an issue in student repos. Args: assignment_names: Names of assignments. teams: Team objects specifying student groups. issue: An issue to open. api: An implementation of :py:class:`repobee_plug.PlatformAPI` used to interface with the platform (e.g. GitHub or GitLab) instance. """ repo_urls = api.get_repo_urls(team_names=[t.name for t in teams], assignment_names=assignment_names) repos = progresswrappers.get_repos(repo_urls, api) for repo in repos: issue = api.create_issue(issue.title, issue.body, repo) msg = f"Opened issue {repo.name}/#{issue.number}-'{issue.title}'" repos.write(msg) # type: ignore plug.log.info(msg)
def assign_peer_reviews( assignment_names: Iterable[str], teams: Iterable[plug.StudentTeam], num_reviews: int, issue: Optional[plug.Issue], double_blind_key: Optional[str], api: plug.PlatformAPI, ) -> None: """Assign peer reviewers among the students to each student repo. Each student is assigned to review num_reviews repos, and consequently, each repo gets reviewed by num_reviews reviewers. In practice, each student repo has a review team generated (called <student-repo-name>-review), to which num_reviews _other_ students are assigned. The team itself is given pull-access to the student repo, so that reviewers can view code and open issues, but cannot modify the contents of the repo. Args: assignment_names: Names of assginments. teams: Team objects specifying student groups. num_reviews: Amount of reviews each student should perform (consequently, the amount of reviews of each repo) issue: An issue with review instructions to be opened in the considered repos. double_blind_key: If provided, use key to make double-blind review allocation. api: An implementation of :py:class:`repobee_plug.PlatformAPI` used to interface with the platform (e.g. GitHub or GitLab) instance. """ issue = issue or DEFAULT_REVIEW_ISSUE expected_repo_names = set(plug.generate_repo_names(teams, assignment_names)) fetched_teams = progresswrappers.get_teams(teams, api, desc="Fetching teams and repos") team_repo_tuples = [(team, list(api.get_team_repos(team))) for team in fetched_teams] fetched_repos = list( itertools.chain.from_iterable(repos for _, repos in team_repo_tuples)) fetched_repo_dict = {r.name: r for r in fetched_repos} missing = expected_repo_names - fetched_repo_dict.keys() if missing: raise plug.NotFoundError(f"Can't find repos: {', '.join(missing)}") if double_blind_key: plug.log.info(f"Creating anonymous repos with key: {double_blind_key}") fetched_repo_dict = _create_anonymized_repos( [(team, _only_expected_repos(repos, expected_repo_names)) for team, repos in team_repo_tuples], double_blind_key, api, ) allocations_for_output = [] for assignment_name in assignment_names: plug.echo("Allocating reviews") allocations = plug.manager.hook.generate_review_allocations( teams=teams, num_reviews=num_reviews) # adjust names of review teams review_team_specs, reviewed_team_names = list( zip(*[( plug.StudentTeam( members=alloc.review_team.members, name=_review_team_name( alloc.reviewed_team, assignment_name, key=double_blind_key, ), ), alloc.reviewed_team, ) for alloc in allocations])) review_teams = _repobee.command.teams.create_teams( review_team_specs, plug.TeamPermission.PULL, api) review_teams_progress = plug.cli.io.progress_bar( review_teams, desc="Creating review teams", total=len(review_team_specs), ) for review_team, reviewed_team_name in zip(review_teams_progress, reviewed_team_names): reviewed_repo = fetched_repo_dict[plug.generate_repo_name( reviewed_team_name, assignment_name)] review_teams_progress.write( # type: ignore f"Assigning {' and '.join(review_team.members)} " f"to review {reviewed_repo.name}") api.assign_repo(review_team, reviewed_repo, plug.TeamPermission.PULL) api.create_issue( issue.title, issue.body, reviewed_repo, # It's not possible to assign users with read-access in Gitea # FIXME redesign so Gitea does not require special handling assignees=review_team.members if not isinstance(api, _repobee.ext.gitea.GiteaAPI) else None, ) allocations_for_output.append({ "reviewed_repo": { "name": reviewed_repo.name, "url": reviewed_repo.url, }, "review_team": { "name": review_team.name, "members": review_team.members, }, }) if featflags.is_feature_enabled( featflags.FeatureFlag.REPOBEE_4_REVIEW_COMMANDS): output = dict(allocations=allocations_for_output, num_reviews=num_reviews) pathlib.Path("review_allocations.json").write_text( json.dumps(output, indent=4), encoding=sys.getdefaultencoding(), )
def assign_peer_reviews( assignment_names: Iterable[str], teams: Iterable[plug.StudentTeam], num_reviews: int, issue: Optional[plug.Issue], api: plug.PlatformAPI, ) -> None: """Assign peer reviewers among the students to each student repo. Each student is assigned to review num_reviews repos, and consequently, each repo gets reviewed by num_reviews reviewers. In practice, each student repo has a review team generated (called <student-repo-name>-review), to which num_reviews _other_ students are assigned. The team itself is given pull-access to the student repo, so that reviewers can view code and open issues, but cannot modify the contents of the repo. Args: assignment_names: Names of assginments. teams: Team objects specifying student groups. num_reviews: Amount of reviews each student should perform (consequently, the amount of reviews of each repo) issue: An issue with review instructions to be opened in the considered repos. api: An implementation of :py:class:`repobee_plug.PlatformAPI` used to interface with the platform (e.g. GitHub or GitLab) instance. """ issue = issue or DEFAULT_REVIEW_ISSUE expected_repo_names = plug.generate_repo_names(teams, assignment_names) fetched_teams = progresswrappers.get_teams(teams, api, desc="Fetching teams and repos") fetched_repos = list( itertools.chain.from_iterable(map(api.get_team_repos, fetched_teams))) fetched_repo_dict = {r.name: r for r in fetched_repos} missing = set(expected_repo_names) - set(fetched_repo_dict.keys()) if missing: raise plug.NotFoundError(f"Can't find repos: {', '.join(missing)}") for assignment_name in assignment_names: plug.echo("Allocating reviews") allocations = plug.manager.hook.generate_review_allocations( teams=teams, num_reviews=num_reviews) # adjust names of review teams review_team_specs, reviewed_team_names = list( zip(*[( plug.StudentTeam( members=alloc.review_team.members, name=plug.generate_review_team_name( str(alloc.reviewed_team), assignment_name), ), alloc.reviewed_team, ) for alloc in allocations])) review_teams = _repobee.command.teams.create_teams( review_team_specs, plug.TeamPermission.PULL, api) review_teams_progress = plug.cli.io.progress_bar( review_teams, desc="Creating review teams", total=len(review_team_specs), ) for review_team, reviewed_team_name in zip(review_teams_progress, reviewed_team_names): reviewed_repo = fetched_repo_dict[plug.generate_repo_name( reviewed_team_name, assignment_name)] review_teams_progress.write( # type: ignore f"Assigning {' and '.join(review_team.members)} " f"to review {reviewed_repo.name}") api.assign_repo(review_team, reviewed_repo, plug.TeamPermission.PULL) api.create_issue( issue.title, issue.body, reviewed_repo, assignees=review_team.members, )