diff --git a/jobserver/views/projects.py b/jobserver/views/projects.py index da8bbc7f1c..851437c2f9 100644 --- a/jobserver/views/projects.py +++ b/jobserver/views/projects.py @@ -1,3 +1,6 @@ +import concurrent +import operator + import requests from django.contrib import messages from django.contrib.auth.decorators import login_required @@ -19,6 +22,9 @@ from ..models import Org, Project, ProjectInvitation, ProjectMembership, Snapshot, User +repo_thread_pool = concurrent.futures.ThreadPoolExecutor(thread_name_prefix="get_repo") + + @method_decorator(login_required, name="dispatch") class ProjectAcceptInvite(View): def get(self, request, *args, **kwargs): @@ -125,13 +131,15 @@ def get_context_data(self, **kwargs): workspaces = self.object.workspaces.order_by("is_archived", "name") - repos = sorted(set(workspaces.values_list("repo", flat=True))) + repos = set(workspaces.values_list("repo", flat=True)) return super().get_context_data(**kwargs) | { "can_create_workspaces": can_create_workspaces, "can_manage_members": can_manage_members, "outputs": self.get_outputs(workspaces), - "repos": list(self.get_repos(repos)), + "repos": list( + sorted(self.iter_repos(repos), key=operator.itemgetter("name")) + ), "workspaces": workspaces, } @@ -161,8 +169,8 @@ def get_outputs(self, workspaces): return Snapshot.objects.filter(pk__in=snapshot_pks).order_by("-published_at") - def get_repos(self, repo_urls): - for url in repo_urls: + def iter_repos(self, repo_urls): + def get_repo(url): f = furl(url) try: @@ -170,12 +178,16 @@ def get_repos(self, repo_urls): except requests.HTTPError: is_private = None - yield { + return { "name": f.path.segments[1], "url": url, "is_private": is_private, } + futures = [repo_thread_pool.submit(get_repo, url=url) for url in repo_urls] + for future in concurrent.futures.as_completed(futures): + yield future.result() + class ProjectInvitationCreate(CreateView): form_class = ProjectInvitationForm