Repository: carpentries/amy

Issue:
Report on all activity should use hyperlinks for workshops
On https://amy.software-carpentry.org/workshops/reports/all_activity_over_time, the references to workshops should be hyperlinks, not just plain-text slugs.

Report on all activity should use hyperlinks for workshops
On https://amy.software-carpentry.org/workshops/reports/all_activity_over_time, the references to workshops should be hyperlinks, not just plain-text slugs.



Discussion:
@narayanaditya95 will tackle this

Found this comment at `api/views.py` L409:

```
        # Workshops missing any of this data.
        # There's no point in using hyperlinks here, because it would:
        # a) require using reverse() unless we somehow managed to switch to
        #    serializer for this view
        # b) make JS part even harder (what is available right now just works)
```

@pbanaszkiewicz How should I proceed in this?

The easy fix would be to return links in API and I'd go with it.

As a human user of the site, I want to be able to click on the slug for a workshop in the list at the bottom of the page reporting problems and be taken to the page for that workshop.

@pbanaszkiewicz 
If we do not plan on extending the all activity report, such that level 3 of the response only corresponds to event slugs, we can construct hyperlinks on the client side (hardcoding here).

Should we go that way?

@narayanaditya95 sorry for not answering to your question earlier. I'm kind of out-of-loop here, so I suggest we postpone this issue to v1.8 and to you I say use your best judgement.

@narayanaditya95 will tackle this

Found this comment at `api/views.py` L409:

```
        # Workshops missing any of this data.
        # There's no point in using hyperlinks here, because it would:
        # a) require using reverse() unless we somehow managed to switch to
        #    serializer for this view
        # b) make JS part even harder (what is available right now just works)
```

@pbanaszkiewicz How should I proceed in this?

The easy fix would be to return links in API and I'd go with it.

As a human user of the site, I want to be able to click on the slug for a workshop in the list at the bottom of the page reporting problems and be taken to the page for that workshop.

@pbanaszkiewicz 
If we do not plan on extending the all activity report, such that level 3 of the response only corresponds to event slugs, we can construct hyperlinks on the client side (hardcoding here).

Should we go that way?

@narayanaditya95 sorry for not answering to your question earlier. I'm kind of out-of-loop here, so I suggest we postpone this issue to v1.8 and to you I say use your best judgement.


Resolving patch:
diff --git a/api/views.py b/api/views.py
--- a/api/views.py
+++ b/api/views.py
@@ -386,6 +386,14 @@ def all_activity_over_time(self, request, format=None):
             start=request.query_params.get('start', None),
             end=request.query_params.get('end', None))
 
+        data = self.get_all_activity_over_time(start, end)
+        data['missing']['attendance'] = self.listify(
+            data['missing']['attendance'], request, format=format)
+        data['missing']['instructors'] = self.listify(
+            data['missing']['instructors'], request, format=format)
+        return Response(data)
+
+    def get_all_activity_over_time(self, start, end):
         events_qs = Event.objects.filter(start__gte=start, start__lte=end)
         swc_tag = Tag.objects.get(name='SWC')
         dc_tag = Tag.objects.get(name='DC')
@@ -422,10 +430,6 @@ def all_activity_over_time(self, request, format=None):
         dc_total_learners = dc_total_learners['count']
 
         # Workshops missing any of this data.
-        # There's no point in using hyperlinks here, because it would:
-        # a) require using reverse() unless we somehow managed to switch to
-        #    serializer for this view
-        # b) make JS part even harder (what is available right now just works)
         missing_attendance = events_qs.filter(attendance=None) \
                                       .values_list('slug', flat=True)
         missing_instructors = events_qs.annotate(
@@ -438,16 +442,26 @@ def all_activity_over_time(self, request, format=None):
             )
         ).filter(instructors=0).values_list('slug', flat=True)
 
-        return Response({
+        return {
             'start': start,
             'end': end,
             'workshops': {
                 'SWC': swc_workshops.count(),
                 'DC': dc_workshops.count(),
+                # This dictionary is traversed in a template where we cannot
+                # write "{{ data.workshops.SWC,DC }}", because commas are
+                # disallowed in templates. Therefore, we include
+                # swc_dc_workshops twice, under two different keys:
+                # - 'SWC,DC' - for backward compatibility,
+                # - 'SWC_or_DC' - so that you can access it in a template.
                 'SWC,DC': swc_dc_workshops,
+                'SWC_or_DC': swc_dc_workshops,
                 'WiSE': wise_workshops,
                 'TTT': ttt_workshops,
+                # We include self_organized_workshops twice, under two
+                # different keys, for the same reason as swc_dc_workshops.
                 'self-organized': self_organized_workshops,
+                'self_organized': self_organized_workshops,
             },
             'instructors': {
                 'SWC': {
@@ -464,14 +478,10 @@ def all_activity_over_time(self, request, format=None):
                 'DC': dc_total_learners,
             },
             'missing': {
-                # qs.values_list returns an iterator, so we need to listify it
-                # for YAML
-                'attendance': self.listify(missing_attendance, request,
-                                           format),
-                'instructors': self.listify(missing_instructors, request,
-                                            format),
+                'attendance': missing_attendance,
+                'instructors': missing_instructors,
             }
-        })
+        }
 
     def instructors_by_time_queryset(self, start, end):
         """Just a queryset to be reused in other view."""
diff --git a/workshops/forms.py b/workshops/forms.py
--- a/workshops/forms.py
+++ b/workshops/forms.py
@@ -1500,3 +1500,16 @@ class Meta:
         fields = [
             'url',
         ]
+
+
+class AllActivityOverTimeForm(forms.Form):
+    start = forms.DateField(
+        label='Begin date as YYYY-MM-DD',
+        input_formats=['%Y-%m-%d', ],
+    )
+    end = forms.DateField(
+        label='End date as YYYY-MD-DD',
+        input_formats=['%Y-%m-%d', ],
+    )
+
+    helper = BootstrapHelper(use_get_method=True)
diff --git a/workshops/views.py b/workshops/views.py
--- a/workshops/views.py
+++ b/workshops/views.py
@@ -86,6 +86,7 @@
     bootstrap_helper_inline_formsets,
     BulkChangeTrainingRequestForm,
     BulkMatchTrainingRequestForm,
+    AllActivityOverTimeForm,
 )
 from workshops.management.commands.check_for_workshop_websites_updates import (
     Command as WebsiteUpdatesCommand,
@@ -2226,11 +2227,28 @@ def instructor_num_taught(request):
 def all_activity_over_time(request):
     """Display number of workshops (of differend kinds), instructors and
     learners over some specific period of time."""
+
+    if 'submit' in request.GET:
+        form = AllActivityOverTimeForm(request.GET)
+
+        if form.is_valid():
+            data = ReportsViewSet().get_all_activity_over_time(
+                start=form.cleaned_data['start'],
+                end=form.cleaned_data['end'],
+            )
+        else:
+            data = None
+    else:
+        form = AllActivityOverTimeForm(initial={
+            'start': datetime.date.today() - datetime.timedelta(days=365),
+            'end': datetime.date.today(),
+        })
+        data = None
+
     context = {
-        'api_endpoint': reverse('api:reports-all-activity-over-time'),
         'title': 'All activity over time',
-        'start': datetime.date.today() - datetime.timedelta(days=365),
-        'end': datetime.date.today(),
+        'form': form,
+        'data': data,
     }
     return render(request, 'workshops/all_activity_over_time.html', context)
 


Test patch: