Skip to content

Commit

Permalink
[feat] Add initial implementation of RepoBee 4 review commands (#868)
Browse files Browse the repository at this point in the history
  • Loading branch information
slarse committed Mar 13, 2021
1 parent 902613d commit 8b0bcd2
Show file tree
Hide file tree
Showing 6 changed files with 322 additions and 56 deletions.
36 changes: 24 additions & 12 deletions src/_repobee/cli/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import repobee_plug as plug

from _repobee import command, exception, formatters, util
from _repobee import command, exception, formatters, util, featflags


def dispatch_command(
Expand Down Expand Up @@ -166,19 +166,31 @@ def _dispatch_reviews_command(
)
return None
elif action == reviews.end:
command.end_reviews(
args.assignments, args.students, args.double_blind_key, api
)
if featflags.is_feature_enabled(
featflags.FeatureFlag.REPOBEE_4_REVIEW_COMMANDS
):
command.peer.end_reviews_repobee_4(args.allocations_file, api)
else:
command.end_reviews(
args.assignments, args.students, args.double_blind_key, api
)
return None
elif action == reviews.check:
command.check_peer_review_progress(
args.assignments,
args.students,
args.title_regex,
args.num_reviews,
args.double_blind_key,
api,
)
if featflags.is_feature_enabled(
featflags.FeatureFlag.REPOBEE_4_REVIEW_COMMANDS
):
command.peer.check_reviews_repobee_4(
args.allocations_file, args.title_regex, api
)
else:
command.check_peer_review_progress(
args.assignments,
args.students,
args.title_regex,
args.num_reviews,
args.double_blind_key,
api,
)
return None
_raise_illegal_action_error(args)

Expand Down
130 changes: 87 additions & 43 deletions src/_repobee/cli/mainparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import _repobee
from _repobee import plugin
from _repobee import config
from _repobee import featflags

from _repobee.cli import argparse_ext

Expand Down Expand Up @@ -423,55 +424,98 @@ def _add_peer_review_parsers(base_parsers, add_parser):
type=str,
)

check_review_progress = add_parser(
plug.cli.CoreCommand.reviews.check,
description=(
"Check which students have opened review review issues in their "
"assigned repos. As it is possible for students to leave the peer "
"review teams on their own, the command checks that each student "
"is assigned to the expected amound of teams. There is currently "
"no way to check if students have been swapped around, so using "
"this command fow grading purposes is not recommended."
),
help="check which students have opened peer review issues",
parents=base_review_parsers,
formatter_class=argparse_ext.OrderedFormatter,
)
check_description = (
"Check which students have opened review review issues in their "
"assigned repos. As it is possible for students to leave the peer "
"review teams on their own, the command checks that each student "
"is assigned to the expected amound of teams. There is currently "
"no way to check if students have been swapped around, so using "
"this command fow grading purposes is not recommended."
)
check_help = (
"the expected amount of reviews each student should be assigned,"
" used to check for team tampering"
)

end_description = (
"Delete review allocations assigned with `assign-reviews`. "
"This is a destructive action, as the allocations for reviews "
"are irreversibly deleted. The purpose of this command is to "
"revoke the reviewers' read access to reviewed repos, and to "
"clean up the allocations (i.e. deleting the review teams when "
"using GitHub, or groups when using GitLab). It will however not "
"do anything with the review issues. You can NOT run "
"`check-reviews` after `end-reviews`, as the former "
"needs the allocations to function properly. Use this command "
"only when reviews are done."
)
end_help = (
"delete review allocations created by `assign-reviews` "
"(DESTRUCTIVE ACTION: read help section before using)"
)

if featflags.is_feature_enabled(
featflags.FeatureFlag.REPOBEE_4_REVIEW_COMMANDS
):
plug.log.warning(
"Activating preview feature "
f"{featflags.FeatureFlag.REPOBEE_4_REVIEW_COMMANDS.value}"
)
allocation_parser = argparse_ext.RepobeeParser(add_help=False)
allocation_parser.add_argument(
"--af",
"--allocations-file",
help="path to an allocations file created by `reviews assign`",
type=pathlib.Path,
required=True,
dest="allocations_file",
)
preview_base_parsers = [base_parsers[0], allocation_parser]

check_review_progress = add_parser(
plug.cli.CoreCommand.reviews.check,
description="Check on the progress of reviews.",
help=check_help,
parents=preview_base_parsers,
)

add_parser(
plug.cli.CoreCommand.reviews.end,
description=end_description,
help=end_help,
parents=preview_base_parsers,
)
else:
check_review_progress = add_parser(
plug.cli.CoreCommand.reviews.check,
description=check_description,
help="check which students have opened peer review issues",
parents=base_review_parsers,
formatter_class=argparse_ext.OrderedFormatter,
)
check_review_progress.add_argument(
"-n",
"--num-reviews",
metavar="N",
help=check_help,
type=int,
required=True,
)

add_parser(
plug.cli.CoreCommand.reviews.end,
description=end_description,
help=end_help,
parents=base_review_parsers,
formatter_class=argparse_ext.OrderedFormatter,
)

check_review_progress.add_argument(
"-r",
"--title-regex",
help="issues matching this regex will count as review issues.",
required=True,
)
check_review_progress.add_argument(
"-n",
"--num-reviews",
metavar="N",
help="the expected amount of reviews each student should be assigned,"
" used to check for team tampering",
type=int,
required=True,
)

add_parser(
plug.cli.CoreCommand.reviews.end,
description=(
"Delete review allocations assigned with `assign-reviews`. "
"This is a destructive action, as the allocations for reviews "
"are irreversibly deleted. The purpose of this command is to "
"revoke the reviewers' read access to reviewed repos, and to "
"clean up the allocations (i.e. deleting the review teams when "
"using GitHub, or groups when using GitLab). It will however not "
"do anything with the review issues. You can NOT run "
"`check-reviews` after `end-reviews`, as the former "
"needs the allocations to function properly. Use this command "
"only when reviews are done."
),
help="delete review allocations created by `assign-reviews` "
"(DESTRUCTIVE ACTION: read help section before using)",
parents=base_review_parsers,
formatter_class=argparse_ext.OrderedFormatter,
)


def _add_issue_parsers(base_parsers, add_parser):
Expand Down
84 changes: 84 additions & 0 deletions src/_repobee/command/peer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import tempfile
import pathlib
import shutil
import sys
import json
from typing import Iterable, Optional, Dict, List, Tuple, Set, Union

import git # type: ignore
Expand All @@ -24,7 +26,9 @@
import _repobee.git
import _repobee.ext.gitea
import _repobee.hash
import _repobee.exception
from _repobee import formatters
from _repobee import featflags

from _repobee.command import progresswrappers

Expand Down Expand Up @@ -96,6 +100,7 @@ def assign_peer_reviews(
api,
)

allocations_for_output = []
for assignment_name in assignment_names:
plug.echo("Allocating reviews")
allocations = plug.manager.hook.generate_review_allocations(
Expand Down Expand Up @@ -155,6 +160,30 @@ def assign_peer_reviews(
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 _only_expected_repos(
repos: List[plug.Repo], expected_repo_names: Set[str]
Expand Down Expand Up @@ -312,6 +341,20 @@ def end_reviews(
)


def end_reviews_repobee_4(
allocations_file: pathlib.Path, api: plug.PlatformAPI
) -> None:
"""Preview version of RepoBee 4's version of :py:fync:`end_reviews`."""
review_allocations = json.loads(
allocations_file.read_text(sys.getdefaultencoding())
)["allocations"]
review_team_names = {
allocation["review_team"]["name"] for allocation in review_allocations
}
for team in progresswrappers.get_teams(review_team_names, api):
api.delete_team(team)


def _delete_anonymous_repos(
assignment_names: Iterable[str],
student_teams: Iterable[plug.StudentTeam],
Expand Down Expand Up @@ -415,6 +458,47 @@ def check_peer_review_progress(
)


def check_reviews_repobee_4(
allocations_file: pathlib.Path, title_regex: str, api: plug.PlatformAPI
) -> None:
"""Preview version of the `reviews check` command for RepoBee 4."""
data = json.loads(allocations_file.read_text(sys.getdefaultencoding()))
review_allocations = data["allocations"]
num_reviews = int(data["num_reviews"])

expected_reviewers = {
allocation["reviewed_repo"]["url"]: allocation["review_team"][
"members"
]
for allocation in review_allocations
}

reviewed_repos = progresswrappers.get_repos(expected_reviewers.keys(), api)
reviews = collections.defaultdict(list)

for reviewed_repo in reviewed_repos:
review_issue_authors = {
issue.author
for issue in api.get_repo_issues(reviewed_repo)
if re.match(title_regex, issue.title)
}
for expected_reviewer in expected_reviewers[reviewed_repo.url]:
reviews[expected_reviewer].append(
plug.Review(
repo=reviewed_repo.name,
done=expected_reviewer in review_issue_authors,
)
)

plug.echo(
formatters.format_peer_review_progress_output(
reviews,
list(itertools.chain.from_iterable(expected_reviewers.values()),),
num_reviews,
)
)


def _review_team_name(
team: Union[str, plug.Team, plug.StudentTeam],
assignment: str,
Expand Down
20 changes: 20 additions & 0 deletions src/_repobee/featflags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Module for dealing with feature flags."""
import os
import enum

__all__ = ["FEATURE_ENABLED_VALUE", "FeatureFlag", "is_feature_enabled"]

FEATURE_ENABLED_VALUE = "true"


class FeatureFlag(enum.Enum):
REPOBEE_4_REVIEW_COMMANDS = "REPOBEE_4_REVIEW_COMMANDS"


def is_feature_enabled(flag: FeatureFlag) -> bool:
"""Check if a feature is enabled.
Args:
flag: A feature flag.
"""
return os.getenv(flag.value) == FEATURE_ENABLED_VALUE
Loading

0 comments on commit 8b0bcd2

Please sign in to comment.