internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: comic/grand-challenge.org

Issue:
Display number of unique users that have submitted to each phase on the challenge stats page
Would be handy for final phase forecasting.


Discussion:
I've got the counts for unique users that have submitted to each phase displaying - I'm just testing it is correct.

Resolving patch:
diff --git a/app/grandchallenge/pages/views.py b/app/grandchallenge/pages/views.py
--- a/app/grandchallenge/pages/views.py
+++ b/app/grandchallenge/pages/views.py
@@ -171,6 +171,13 @@ def get_context_data(self, **kwargs):
             .order_by("created__year", "created__month", "phase__pk")
         )
 
+        creators = (
+            Submission.objects.filter(phase__in=public_phases)
+            .values("phase__pk")
+            .annotate(creators_count=Count("creator"))
+            .order_by("phase__pk")
+        )
+
         context.update(
             {
                 "participants": world_map(
@@ -213,6 +220,21 @@ def get_context_data(self, **kwargs):
                     short_name=self.request.challenge.short_name,
                     status=ChallengeRequest.ChallengeRequestStatusChoices.ACCEPTED,
                 ).first(),
+                "creators": stacked_bar(
+                    values=[
+                        {
+                            "Creators": datum["creators_count"],
+                            "Phase": datum["phase__pk"],
+                        }
+                        for datum in creators
+                    ],
+                    lookup="Creators",
+                    title="Creators per Phase",
+                    facet="Phase",
+                    domain=[
+                        (phase.pk, phase.title) for phase in public_phases
+                    ],
+                ),
             }
         )
 


Test patch:
Repository: comic/grand-challenge.org

Issue:
Add count of unique submitters per phase to statistics
Closes #3029


Discussion:

Resolving patch:
diff --git a/app/config/settings.py b/app/config/settings.py
--- a/app/config/settings.py
+++ b/app/config/settings.py
@@ -986,6 +986,7 @@
     "visibility_timeout": int(1.1 * CELERY_TASK_TIME_LIMIT)
 }
 CELERY_BROKER_CONNECTION_MAX_RETRIES = 0
+CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True
 
 if os.environ.get("BROKER_TYPE", "").lower() == "sqs":
     CELERY_BROKER_URL = "sqs://"
diff --git a/app/grandchallenge/pages/views.py b/app/grandchallenge/pages/views.py
--- a/app/grandchallenge/pages/views.py
+++ b/app/grandchallenge/pages/views.py
@@ -174,7 +174,7 @@ def get_context_data(self, **kwargs):
         creators = (
             Submission.objects.filter(phase__in=public_phases)
             .values("phase__pk")
-            .annotate(creators_count=Count("creator"))
+            .annotate(creators_count=Count("creator__pk", distinct=True))
             .order_by("phase__pk")
         )
 


Test patch:
Repository: comic/grand-challenge.org

Issue:
Add Question Page has a default interface selected
When loading the page this field shouldn't have anything selected:

<img width="249" alt="Screenshot 2023-11-01 at 12 13 30" src="https://github.com/comic/grand-challenge.org/assets/12661555/049d665e-57c2-4632-8f69-7d7280fcd9d9">


The question update view also needs to be checked.


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/reader_studies/forms.py b/app/grandchallenge/reader_studies/forms.py
--- a/app/grandchallenge/reader_studies/forms.py
+++ b/app/grandchallenge/reader_studies/forms.py
@@ -333,9 +333,6 @@ def interface_choices(self):
             kind__in=ANSWER_TYPE_TO_INTERFACE_KIND_MAP[answer_type]
         )
 
-    def initial_interface(self):
-        return self.interface_choices().first()
-
     def widget_choices(self):
         answer_type = self["answer_type"].value()
         choices = [*BLANK_CHOICE_DASH]
@@ -453,7 +450,7 @@ class Meta:
     interface = DynamicField(
         ModelChoiceField,
         queryset=interface_choices,
-        initial=initial_interface,
+        initial=None,
         required=False,
         help_text="Select component interface to use as a default answer for this "
         "question.",


Test patch:
Repository: comic/grand-challenge.org

Issue:
Additional metrics buttons are displayed even when there are no additional metrics
<img width="442" alt="Screenshot 2023-09-14 at 16 29 41" src="https://github.com/comic/grand-challenge.org/assets/12661555/aeef3926-ed8d-458c-9d86-7b266ef38301">

https://surgtoolloc23.grand-challenge.org/evaluation/final-testing-phase/leaderboard/


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/evaluation/views/__init__.py b/app/grandchallenge/evaluation/views/__init__.py
--- a/app/grandchallenge/evaluation/views/__init__.py
+++ b/app/grandchallenge/evaluation/views/__init__.py
@@ -742,7 +742,7 @@ def get_context_data(self, *args, **kwargs):
                 "user_teams": self.user_teams,
                 "allow_metrics_toggling": len(self.phase.extra_results_columns)
                 > 0
-                or self.phase.scoring_method_choice is not self.phase.ABSOLUTE,
+                or self.phase.scoring_method_choice != self.phase.ABSOLUTE,
                 "display_leaderboard_date_button": self.phase.result_display_choice
                 == self.phase.ALL,
             }


Test patch:
Repository: comic/grand-challenge.org

Issue:
Reader study question of type number can't be updated.
**Describe the bug**
Updating the direction of a reader study question of type Number is not possible when ground truth or user answers exist.


**To Reproduce**
Steps to reproduce the behavior:
1. Go to https://grand-challenge.org/reader-studies/cirruscoreweb-release-display-sets-editable/
2. Click on `Questions`
3. Click on Edit of the question with text "How many characters do you see?
4. Edit Direction or order and click `Save`.
5. Change is not saved, the following error is displayed: (widget was showing correct value before saving)
![image](https://github.com/comic/grand-challenge.org/assets/51329283/665b553f-06bc-440e-a779-3e50ca4a52d6)



**Expected behavior**
The changes should be saved.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: [e.g. iOS]
 - Browser [e.g. chrome, safari]
 - Version [e.g. 22]

**Smartphone (please complete the following information):**
 - Device: [e.g. iPhone6]
 - OS: [e.g. iOS8.1]
 - Browser [e.g. stock browser, safari]
 - Version [e.g. 22]

**Additional context**
Add any other context about the problem here.



Discussion:

Resolving patch:
diff --git a/app/grandchallenge/reader_studies/forms.py b/app/grandchallenge/reader_studies/forms.py
--- a/app/grandchallenge/reader_studies/forms.py
+++ b/app/grandchallenge/reader_studies/forms.py
@@ -334,7 +334,12 @@ def interface_choices(self):
         )
 
     def widget_choices(self):
-        answer_type = self["answer_type"].value()
+        if not self.instance.is_fully_editable:
+            # disabled form elements are not sent along with the form,
+            # so retrieve the answer type from the instance
+            answer_type = self.instance.answer_type
+        else:
+            answer_type = self["answer_type"].value()
         choices = [*BLANK_CHOICE_DASH]
 
         if not answer_type:


Test patch:
diff --git a/app/tests/reader_studies_tests/test_forms.py b/app/tests/reader_studies_tests/test_forms.py
--- a/app/tests/reader_studies_tests/test_forms.py
+++ b/app/tests/reader_studies_tests/test_forms.py
@@ -1309,3 +1309,20 @@ def test_question_form_initial_widget():
     qu.widget = QuestionWidgetKindChoices.ACCEPT_REJECT
     form2 = QuestionForm(instance=qu)
     assert form2.initial_widget() == QuestionWidgetKindChoices.ACCEPT_REJECT
+
+
+@pytest.mark.django_db
+def test_question_widget_choices_for_non_editable_instance():
+    # no matter whether the question is editable, widget choices should be the same
+    qu = QuestionFactory(answer_type=AnswerType.TEXT)
+    form = QuestionForm(instance=qu)
+    assert form.widget_choices() == [
+        ("TEXT_INPUT", "Text Input"),
+        ("TEXT_AREA", "Text Area"),
+    ]
+    AnswerFactory(question=qu, answer="Foo")
+    form = QuestionForm(instance=qu)
+    assert form.widget_choices() == [
+        ("TEXT_INPUT", "Text Input"),
+        ("TEXT_AREA", "Text Area"),
+    ]
Repository: comic/grand-challenge.org

Issue:
Missing default for answer roll over field in reader studies
When creating a reader study, the answer roll over field is required but empty by default. The help text does not clearly mention that it should be set to 0 to "disable" the feature. I think this should be added to the help text and the default value should be set to 0.


Discussion:
The default for the field is set to `False`.

https://github.com/comic/grand-challenge.org/blob/72153c085ed6cec5708b5288b7f5a77a257aa6ca/app/grandchallenge/reader_studies/models.py#L280-L288

This does not make sense for the field so I think it's a copy paste error. 

Resolving patch:
diff --git a/app/grandchallenge/reader_studies/migrations/0049_alter_readerstudy_roll_over_answers_for_n_cases.py b/app/grandchallenge/reader_studies/migrations/0049_alter_readerstudy_roll_over_answers_for_n_cases.py
new file mode 100644
--- /dev/null
+++ b/app/grandchallenge/reader_studies/migrations/0049_alter_readerstudy_roll_over_answers_for_n_cases.py
@@ -0,0 +1,24 @@
+# Generated by Django 4.2.9 on 2024-01-09 14:17
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        (
+            "reader_studies",
+            "0048_alter_answer_answer_alter_historicalanswer_answer_and_more",
+        ),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="readerstudy",
+            name="roll_over_answers_for_n_cases",
+            field=models.PositiveSmallIntegerField(
+                default=0,
+                help_text="The number of cases for which answers should roll over. It can be used for repeated readings with slightly different hangings. For instance, if set to 1. Case 2 will start with the answers from case 1; whereas case 3 starts anew but its answers will roll over to case 4. Setting it to 0 (default) means answers will not roll over.",
+            ),
+        ),
+    ]
diff --git a/app/grandchallenge/reader_studies/models.py b/app/grandchallenge/reader_studies/models.py
--- a/app/grandchallenge/reader_studies/models.py
+++ b/app/grandchallenge/reader_studies/models.py
@@ -278,12 +278,13 @@ class ReaderStudy(UUIDModel, TitleSlugDescriptionModel, ViewContentMixin):
         ),
     )
     roll_over_answers_for_n_cases = models.PositiveSmallIntegerField(
-        default=False,
+        default=0,
         help_text=(
             "The number of cases for which answers should roll over. "
             "It can be used for repeated readings with slightly different hangings. "
             "For instance, if set to 1. Case 2 will start with the answers from case 1; "
-            "whereas case 3 starts anew but its answers will rollover to case 4."
+            "whereas case 3 starts anew but its answers will roll over to case 4. "
+            "Setting it to 0 (default) means answers will not roll over."
         ),
     )
     publications = models.ManyToManyField(


Test patch:
Repository: comic/grand-challenge.org

Issue:
Small issues with ground truth CSV files for reader studies
- [x] The example ground truth CSV file does not have an extension when saving it.
- [x] There is no example answer for:
  - [x] Number
  - [x] Text
- [ ] ~Help text for uploading a CSV indicates semicolons should be used as separators, but this should be commas.~ Misread help text.


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/reader_studies/models.py b/app/grandchallenge/reader_studies/models.py
--- a/app/grandchallenge/reader_studies/models.py
+++ b/app/grandchallenge/reader_studies/models.py
@@ -1260,6 +1260,8 @@ class ScoringFunction(models.TextChoices):
     EXAMPLE_FOR_ANSWER_TYPE = {
         AnswerType.SINGLE_LINE_TEXT: "'\"answer\"'",
         AnswerType.MULTI_LINE_TEXT: "'\"answer\\nanswer\\nanswer\"'",
+        AnswerType.TEXT: "'\"answer\"'",
+        AnswerType.NUMBER: "'1'",
         AnswerType.BOOL: "'true'",
         AnswerType.CHOICE: "'\"option\"'",
         AnswerType.MULTIPLE_CHOICE: '\'["option1", "option2"]\'',
diff --git a/app/grandchallenge/reader_studies/views.py b/app/grandchallenge/reader_studies/views.py
--- a/app/grandchallenge/reader_studies/views.py
+++ b/app/grandchallenge/reader_studies/views.py
@@ -192,7 +192,7 @@ def get(self, request, *args, **kwargs):
         response = HttpResponse(content_type="text/csv")
         response[
             "Content-Disposition"
-        ] = f'attachment; filename="ground-truth-{reader_study.slug}"'
+        ] = f'attachment; filename="ground-truth-{reader_study.slug}.csv"'
         writer = csv.DictWriter(
             response,
             fieldnames=reader_study.ground_truth_file_headers,


Test patch:
Repository: comic/grand-challenge.org

Issue:
Missing styling on the google-login page
When using the "login with Google" option on grand-challenge.org, the intermediate page that is shown for the redirect to google is completely unstyled:

![image](https://github.com/comic/grand-challenge.org/assets/11632379/625f734f-332b-499b-ad48-cfb621b82822)

Network tab reveals that no attempt at loading any stylesheet is being made:

![image](https://github.com/comic/grand-challenge.org/assets/11632379/ac739447-b16d-4c96-aeb2-414ae1b6f037)



Discussion:
Hint: many of the template names changed in https://github.com/pennersr/django-allauth/commit/ff1a6a38e14b0542a23362e650eb8d88e8edd73a#diff-9d7989b4e4a4f0901f83235eba1094e764959f7b85f7c78e2c0799527144c49c. It's not quite clear to me how to go about migrating there. The advice in the docs is to copy everything over? Doesn't seem sensible to me given future potential mass changes like this https://docs.allauth.org/en/0.59.0/common/templates.html
Ran into this locally on the post registration page. 

Might be related:
<img width="1363" alt="image" src="https://github.com/comic/grand-challenge.org/assets/7871310/0d19dc6f-169f-41b6-818d-01a3aebbb464">

Following these instructions https://docs.allauth.org/en/latest/common/templates.html

Resolving patch:
diff --git a/app/config/settings.py b/app/config/settings.py
--- a/app/config/settings.py
+++ b/app/config/settings.py
@@ -622,6 +622,11 @@ def get_private_ip():
 ##############################################################################
 #
 # django-allauth
+# https://docs.allauth.org/en/latest/account/configuration.html
+# https://docs.allauth.org/en/latest/socialaccount/configuration.html
+# https://docs.allauth.org/en/latest/mfa/configuration.html
+# https://docs.allauth.org/en/latest/usersessions/configuration.html
+# https://docs.allauth.org/en/latest/common/configuration.html
 #
 ##############################################################################
 
@@ -632,6 +637,8 @@ def get_private_ip():
 ACCOUNT_AUTHENTICATION_METHOD = "username_email"
 ACCOUNT_EMAIL_REQUIRED = True
 ACCOUNT_EMAIL_VERIFICATION = "mandatory"
+ACCOUNT_CHANGE_EMAIL = True
+ACCOUNT_CONFIRM_EMAIL_ON_GET = False
 ACCOUNT_USERNAME_MIN_LENGTH = 4
 ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https"
 ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
@@ -639,6 +646,7 @@ def get_private_ip():
 ACCOUNT_USERNAME_VALIDATORS = (
     "grandchallenge.profiles.validators.username_validators"
 )
+
 SOCIALACCOUNT_ADAPTER = "grandchallenge.profiles.adapters.SocialAccountAdapter"
 SOCIALACCOUNT_AUTO_SIGNUP = False
 SOCIALACCOUNT_STORE_TOKENS = False


Test patch:
Repository: comic/grand-challenge.org

Issue:
Consolidate container image deletion
The manifests endpoint on ECR's OCI / Docker Registry API now supports delete, so we should be able to consolidate this method:

https://github.com/comic/grand-challenge.org/blob/ccc573d33a4de1765e4941851f92bf4e90479266/app/grandchallenge/components/tasks.py#L269-L293


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/components/tasks.py b/app/grandchallenge/components/tasks.py
--- a/app/grandchallenge/components/tasks.py
+++ b/app/grandchallenge/components/tasks.py
@@ -11,7 +11,6 @@
 from pathlib import Path
 from tempfile import NamedTemporaryFile, TemporaryDirectory
 
-import boto3
 from billiard.exceptions import SoftTimeLimitExceeded, TimeLimitExceeded
 from celery import shared_task
 from celery.exceptions import MaxRetriesExceededError
@@ -253,30 +252,13 @@ def push_container_image(*, instance):
 
 
 def remove_tag_from_registry(*, repo_tag):
-    if settings.COMPONENTS_REGISTRY_INSECURE:
-        digest_cmd = _repo_login_and_run(command=["crane", "digest", repo_tag])
+    digest_cmd = _repo_login_and_run(command=["crane", "digest", repo_tag])
 
-        digests = digest_cmd.stdout.decode("utf-8").splitlines()
+    digests = digest_cmd.stdout.decode("utf-8").splitlines()
 
-        for digest in digests:
-            _repo_login_and_run(
-                command=["crane", "delete", f"{repo_tag}@{digest}"]
-            )
-    else:
-        client = boto3.client(
-            "ecr", region_name=settings.COMPONENTS_AMAZON_ECR_REGION
-        )
-
-        repo_name, image_tag = repo_tag.rsplit(":", 1)
-        repo_name = repo_name.replace(
-            f"{settings.COMPONENTS_REGISTRY_URL}/", "", 1
-        )
-
-        client.batch_delete_image(
-            repositoryName=repo_name,
-            imageIds=[
-                {"imageTag": image_tag},
-            ],
+    for digest in digests:
+        _repo_login_and_run(
+            command=["crane", "delete", f"{repo_tag}@{digest}"]
         )
 
 


Test patch:
Repository: comic/grand-challenge.org

Issue:
Cards jump up and down if you leave the homepage open
Go to https://grand-challenge.org and leave it open for a couple of minutes, watch the cards jump as the carousel automatically changes.


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/core/views.py b/app/grandchallenge/core/views.py
--- a/app/grandchallenge/core/views.py
+++ b/app/grandchallenge/core/views.py
@@ -127,7 +127,7 @@ def get_context_data(self, **kwargs):
             .exclude(highlight=True)
             .first()
         )
-        news_caroussel_items = [
+        news_carousel_items = [
             item
             for item in [
                 latest_news_item,
@@ -161,7 +161,7 @@ def get_context_data(self, **kwargs):
                 .prefetch_related("publications")
                 .order_by("-created")
                 .all()[:4],
-                "news_caroussel_items": news_caroussel_items,
+                "news_carousel_items": news_carousel_items,
                 "latest_news_item": latest_news_item,
             }
         )


Test patch:
Repository: comic/grand-challenge.org

Issue:
Unsupported upload interface type breaks later file uploads widgets
I have a reader study with a 3d-liver-model interface (.obj kind). The following likely also extends to archive items.

We don't support uploading these via the UI.

However, when one of these interfaces is present all subsequent file upload widgets are not loaded on page: https://grand-challenge.org/reader-studies/chris-test-reader-study-the-second-coming/display-sets/create-single/

![image](https://github.com/comic/grand-challenge.org/assets/7871310/7b167978-def3-46b3-b301-ecb9cda7b24e)

I've since removed the display set with the interface to quickly do a workaround.

Not sure if this needs to be fixed or is so corner case that we can safely ignore it.


Discussion:
Looking at it quickly, I think this might have to do with the fact that the interface name starts with a number.
The same happens with the 2D liver contours interface, which is a json file interface, which we do support normally. I'll work on a fix next week.

Resolving patch:
diff --git a/app/grandchallenge/uploads/widgets.py b/app/grandchallenge/uploads/widgets.py
--- a/app/grandchallenge/uploads/widgets.py
+++ b/app/grandchallenge/uploads/widgets.py
@@ -11,8 +11,10 @@ def __init__(self, *args, allowed_file_types=None, **kwargs):
 
     def get_context(self, *args, **kwargs):
         context = super().get_context(*args, **kwargs)
+        widget_id = f'X_{context["widget"]["attrs"]["id"]}'
+        context["widget"]["attrs"]["id"] = widget_id
         context["widget"]["allowed_file_types"] = {
-            "id": f"{context['widget']['attrs']['id']}AllowedFileTypes",
+            "id": f"{widget_id}AllowedFileTypes",
             "value": self.allowed_file_types,
         }
         return context


Test patch:
diff --git a/app/tests/algorithms_tests/test_forms.py b/app/tests/algorithms_tests/test_forms.py
--- a/app/tests/algorithms_tests/test_forms.py
+++ b/app/tests/algorithms_tests/test_forms.py
@@ -250,7 +250,7 @@ def try_create_algorithm():
             "anything",
             [
                 'class="user-upload"',
-                '<div id="id_anything-drag-drop"',
+                '<div id="X_id_anything-drag-drop"',
             ],
         ),
     ),
Repository: comic/grand-challenge.org

Issue:
Cannot delete notifications
**Describe the bug**
It is no longer possible for the user to delete their notifications. 

**To Reproduce**
Steps to reproduce the behavior:
1. Go to https://grand-challenge.org/notifications/
2. Select notifications
3. Click on "Delete selected"
4. Nothing happens
5. The console reveals that the CSRF token is missing

**Expected behavior**
The notifications should get deleted. The CSRF token should automatically be sent along with the ajax request. 


This used to work not too long ago. I can't readily identify what happened that broke this. Also couldn't fix it quickly. Needs some more investigation. 



Discussion:
Same holds for Mark as read.

Resolving patch:
diff --git a/app/grandchallenge/core/templatetags/csrf.py b/app/grandchallenge/core/templatetags/csrf.py
new file mode 100644
--- /dev/null
+++ b/app/grandchallenge/core/templatetags/csrf.py
@@ -0,0 +1,11 @@
+from django import template
+
+register = template.Library()
+
+
+@register.simple_tag
+def csrf_data(csrf_token, csrf_header_name):
+    return {
+        "csrfHeaderName": csrf_header_name,
+        "csrfToken": str(csrf_token),
+    }


Test patch:
Repository: comic/grand-challenge.org

Issue:
Viewer configuration does not show linking options
**Describe the bug**
The view and edit pages for viewer configurations no longer show options to set the linking configuration.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to https://grand-challenge.org/viewer-configurations/demo-rse/
2. Scroll down to 'Linking Configuration'
The options displayed are duplicates of the 'Plugin and Tools' section.

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: [e.g. iOS]
 - Browser [e.g. chrome, safari]
 - Version [e.g. 22]

**Smartphone (please complete the following information):**
 - Device: [e.g. iPhone6]
 - OS: [e.g. iOS8.1]
 - Browser [e.g. stock browser, safari]
 - Version [e.g. 22]

**Additional context**
Add any other context about the problem here.



Discussion:

Resolving patch:
diff --git a/app/grandchallenge/workstation_configs/forms.py b/app/grandchallenge/workstation_configs/forms.py
--- a/app/grandchallenge/workstation_configs/forms.py
+++ b/app/grandchallenge/workstation_configs/forms.py
@@ -79,7 +79,7 @@ def __init__(self, *args, read_only=False, **kwargs):
             ),
             Fieldset(
                 "Linking Configuration",
-                *PLUGIN_FIELDS,
+                *LINKED_FIELDS,
                 css_class="border rounded px-2 my-4",
             ),
         )


Test patch: