From 98d8a57c0c395d8c77d60bd4a100449b1b06b03e Mon Sep 17 00:00:00 2001 From: DarshanCode2005 Date: Fri, 13 Mar 2026 13:43:31 +0530 Subject: [PATCH 1/5] feat(models): add publisher field to NixpkgsIssue --- .../migrations/0071_nixpkgsissue_publisher.py | 21 +++++++++++++++++++ src/shared/models/issue.py | 11 +++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/shared/migrations/0071_nixpkgsissue_publisher.py diff --git a/src/shared/migrations/0071_nixpkgsissue_publisher.py b/src/shared/migrations/0071_nixpkgsissue_publisher.py new file mode 100644 index 000000000..6b4fe5e81 --- /dev/null +++ b/src/shared/migrations/0071_nixpkgsissue_publisher.py @@ -0,0 +1,21 @@ +# Generated by Django 5.2.9 on 2026-03-12 17:50 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('shared', '0070_cvederivationclusterproposal_rejection_reason'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='nixpkgsissue', + name='publisher', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='published_issues', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/src/shared/models/issue.py b/src/shared/models/issue.py index 56670e234..d93a3fdfe 100644 --- a/src/shared/models/issue.py +++ b/src/shared/models/issue.py @@ -4,6 +4,7 @@ from django.core.exceptions import ValidationError from django.db import IntegrityError, models +from django.conf import settings from django.db.models.signals import post_save from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ @@ -38,6 +39,13 @@ class NixpkgsIssue(models.Model): suggestion = models.OneToOneField( CVEDerivationClusterProposal, on_delete=models.PROTECT ) + publisher = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name="published_issues", + ) status = models.CharField( max_length=text_length(IssueStatus), @@ -61,7 +69,7 @@ def status_string(self) -> str: @classmethod def create_nixpkgs_issue( - cls, suggestion: CVEDerivationClusterProposal + cls, suggestion: CVEDerivationClusterProposal, publisher=None ) -> "NixpkgsIssue": """ Create a NixpkgsIssue from a suggestion and save it in the database. Note @@ -75,6 +83,7 @@ def create_nixpkgs_issue( # end. status=IssueStatus.AFFECTED, suggestion=suggestion, + publisher=publisher, ) issue.save() return issue From 36ec12190b956403ee126beb57b733728a4d6191 Mon Sep 17 00:00:00 2001 From: DarshanCode2005 Date: Fri, 13 Mar 2026 14:04:56 +0530 Subject: [PATCH 2/5] feat(webview): capture logged-in user as issue publisher --- src/webview/suggestions/views/status.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/webview/suggestions/views/status.py b/src/webview/suggestions/views/status.py index 1e894071e..4ce34b038 100644 --- a/src/webview/suggestions/views/status.py +++ b/src/webview/suggestions/views/status.py @@ -76,7 +76,7 @@ def post(self, request: HttpRequest, suggestion_id: int) -> HttpResponse: elif new_status == "published": try: with transaction.atomic(): - tracker_issue = NixpkgsIssue.create_nixpkgs_issue(suggestion) + tracker_issue = NixpkgsIssue.create_nixpkgs_issue(suggestion, publisher=request.user) tracker_issue_link = request.build_absolute_uri( reverse("webview:issue_detail", args=[tracker_issue.code]) ) @@ -84,6 +84,7 @@ def post(self, request: HttpRequest, suggestion_id: int) -> HttpResponse: suggestion_context.suggestion.cached, tracker_issue_link, new_comment, + publisher=request.user, ).html_url NixpkgsEvent.objects.create( issue=tracker_issue, From ebda9cd7780c31dc580d170affcf8b63a79e7d4d Mon Sep 17 00:00:00 2001 From: DarshanCode2005 Date: Fri, 13 Mar 2026 14:05:37 +0530 Subject: [PATCH 3/5] feat(github): append publisher username to issue body --- src/shared/github.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shared/github.py b/src/shared/github.py index 89ba433f5..ef71bd448 100644 --- a/src/shared/github.py +++ b/src/shared/github.py @@ -28,6 +28,7 @@ def create_gh_issue( cached_suggestion: CachedSuggestions, tracker_issue_uri: str, comment: str | None = None, + publisher=None, # FIXME(@fricklerhandwerk): [tag:todo-github-connection] Make an application-level "GitHub connection" object instead. # Instantiating the connection at definition time makes mocking it away for tests rather cumbersome. # Ideally we'd have a generic mock that would abstract away regular book keeping such as app authentication, and tests would override only relevant behavior. @@ -150,8 +151,9 @@ def additional_comment() -> str: cached_suggestion.payload["pk"], ) + publisher_str = f" Published by @{publisher.username}" if publisher else "" body = f"""\ -- [{cached_suggestion.payload["cve_id"]}](https://nvd.nist.gov/vuln/detail/{quote(cached_suggestion.payload["cve_id"])}) +- [{cached_suggestion.payload["cve_id"]}](https://nvd.nist.gov/vuln/detail/{quote(cached_suggestion.payload["cve_id"])}){publisher_str} - [Nixpkgs security tracker issue]({tracker_issue_uri}) {maintainers()} ## Description From 44445262ee839394d1270c114e73179bf3f24e9c Mon Sep 17 00:00:00 2001 From: DarshanCode2005 Date: Fri, 13 Mar 2026 14:07:02 +0530 Subject: [PATCH 4/5] feat(ui): display issue publisher with Github profile link --- src/webview/templates/components/issue.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webview/templates/components/issue.html b/src/webview/templates/components/issue.html index 3f746de55..4ccdb9277 100644 --- a/src/webview/templates/components/issue.html +++ b/src/webview/templates/components/issue.html @@ -10,7 +10,7 @@ {% if github_issue %} {% endif %} - published on {{ issue.created }} + published {% if issue.publisher %}by @{{ issue.publisher.username }} {% endif %}on {{ issue.created }} From c24f7c9510a971c8914cf6eb4874b07d8865921d Mon Sep 17 00:00:00 2001 From: DarshanCode2005 Date: Fri, 13 Mar 2026 14:07:56 +0530 Subject: [PATCH 5/5] test: verify publisher tracking and display for issues --- src/webview/tests/test_issues.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/webview/tests/test_issues.py b/src/webview/tests/test_issues.py index 38c878b39..2b442a621 100644 --- a/src/webview/tests/test_issues.py +++ b/src/webview/tests/test_issues.py @@ -71,6 +71,9 @@ def test_publish_gh_issue_empty_title( link = as_staff.get_by_role("link", name="View") expect(link).to_be_visible() mock.assert_called() + + # Check publisher name exists in kwargs + assert mock.call_args[1]["publisher"].username == "staff" if no_js: error = as_staff.locator("#messages") @@ -89,6 +92,7 @@ def test_publish_gh_issue_empty_title( issue_link = suggestion.locator("..").get_by_role("link", name="GitHub issue") expect(issue_link).to_be_visible() + expect(suggestion.locator("..").filter(has_text="by @staff")).to_be_visible() # FIXME(@fricklerhandwerk): Instrument the GitHub mock to produce a controlled link and check for that in the UI. # This would assert we're actually displaying the right URL. expect(issue_link).not_to_have_attribute("href", "") @@ -177,3 +181,5 @@ def mock_get_maintainer_username( assert f"@{maintainer_handle}" not in issue_body else: assert f"@{maintainer_handle}" in issue_body + + assert "Published by @staff" in issue_body