diff --git a/assets/src/js/repos-with-multiple-projects-tablesorter.js b/assets/src/js/repos-with-multiple-projects-tablesorter.js new file mode 100644 index 000000000..d6fa53a5e --- /dev/null +++ b/assets/src/js/repos-with-multiple-projects-tablesorter.js @@ -0,0 +1,22 @@ +/* global $ */ +$(() => { + $("table").tablesorter({ + theme: "bootstrap", + widthFixed: true, + sortReset: true, + widgets: ["filter", "columns"], + headers: { + 0: { sortInitialOrder: "desc" }, + }, + widgetOptions: { + columns: ["table-info"], + filter_reset: ".reset", + filter_cssFilter: [ + "form-control", + "form-control", + "form-control", + "form-control", + ], + }, + }); +}); diff --git a/staff/urls.py b/staff/urls.py index 4b674df27..1f40f9366 100644 --- a/staff/urls.py +++ b/staff/urls.py @@ -19,7 +19,7 @@ from .views.dashboards.copiloting import Copiloting from .views.dashboards.index import DashboardIndex from .views.dashboards.projects import ProjectsDashboard -from .views.dashboards.repos import PrivateReposDashboard +from .views.dashboards.repos import PrivateReposDashboard, ReposWithMultipleProjects from .views.index import Index from .views.orgs import ( OrgCreate, @@ -91,7 +91,12 @@ path("", DashboardIndex.as_view(), name="index"), path("copiloting/", Copiloting.as_view(), name="copiloting"), path("project/", ProjectsDashboard.as_view(), name="projects"), - path("repos", PrivateReposDashboard.as_view(), name="repos"), + path("repos/", PrivateReposDashboard.as_view(), name="repos"), + path( + "repos-with-multiple-projects/", + ReposWithMultipleProjects.as_view(), + name="repos-with-multiple-projects", + ), ] org_urls = [ diff --git a/staff/views/dashboards/repos.py b/staff/views/dashboards/repos.py index ea659f78d..82caa6d1e 100644 --- a/staff/views/dashboards/repos.py +++ b/staff/views/dashboards/repos.py @@ -3,8 +3,8 @@ import structlog from csp.decorators import csp_exempt -from django.db.models import Count, Min -from django.db.models.functions import Least +from django.db.models import Count, Min, Prefetch +from django.db.models.functions import Least, Lower from django.template.response import TemplateResponse from django.utils import timezone from django.utils.decorators import method_decorator @@ -14,7 +14,7 @@ from jobserver.authorization import CoreDeveloper from jobserver.authorization.decorators import require_role from jobserver.github import _get_github_api -from jobserver.models import Project, Workspace +from jobserver.models import Project, Repo, Workspace logger = structlog.get_logger(__name__) @@ -136,3 +136,44 @@ def select(repo): return TemplateResponse( request, "staff/dashboards/repos.html", {"repos": repos} ) + + +@method_decorator(require_role(CoreDeveloper), name="dispatch") +class ReposWithMultipleProjects(View): + @csp_exempt + def get(self, request, *args, **kwargs): + def iter_repos(): + repos = ( + Repo.objects.annotate( + project_count=Count("workspaces__project", distinct=True) + ) + .filter(project_count__gte=2) + .prefetch_related( + Prefetch( + "workspaces", + Workspace.objects.order_by("name"), + to_attr="ordered_workspaces", + ), + # Prefetch( + # "workspaces__project", Project.objects.all(), to_attr="derp" + # ), + ) + .order_by(Lower("url")) + ) + + for repo in repos: + projects = Project.objects.filter(workspaces__repo=repo).distinct() + yield { + "has_github_outputs": repo.has_github_outputs, + "name": repo.name, + "projects": projects, + "quoted_url": repo.quoted_url, + "workspaces": repo.ordered_workspaces, + } + + repos = list(iter_repos()) + return TemplateResponse( + request, + "staff/dashboards/repos_with_multiple_projects.html", + {"repos": repos}, + ) diff --git a/templates/staff/dashboards/index.html b/templates/staff/dashboards/index.html index 526f7edf7..7da379dd9 100644 --- a/templates/staff/dashboards/index.html +++ b/templates/staff/dashboards/index.html @@ -50,7 +50,7 @@
@@ -61,6 +61,16 @@
+ Repos with multiple projects on OpenSAFELY +
+ +Remaining: {{ repos|length }}
+Repo | +Projects | +Workspaces | +Files released to GitHub | +|
---|---|---|---|---|
{{ repo.name }} | + +
+
+
+ + Show + Hide + {{ repo.projects|length }} projects ++
|
+
+
+ {% if repo.workspaces|length > 1 %}
+
+
+ {% else %}
+ {% for workspace in repo.workspaces %}
+
+ {{ workspace.name }}
+
+ {% endfor %}
+ {% endif %}
+ + Show + Hide + {{ repo.workspaces|length }} workspaces ++
|
+
+ {% if repo.has_github_outputs %}
+ YES | + {% else %} +No | + {% endif %} +