Skip to content

Commit 3f75dae

Browse files
feat(nimbus): Filter by date (#13481)
Because - We want to allow users to filter by the dates in my deliveries section on the new home page This commit - Add filters on the dates to filter by options like last 7 days, last month etc and also able to enter the range either start date or end date or both the dates Fixes #13476
1 parent 29f5e89 commit 3f75dae

File tree

4 files changed

+460
-3
lines changed

4 files changed

+460
-3
lines changed

experimenter/experimenter/nimbus_ui/filtersets.py

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from datetime import date, datetime, timedelta
2+
13
import django_filters
24
from django import forms
35
from django.contrib.auth.models import User
@@ -95,6 +97,15 @@ def get_context(self, name, value, attrs):
9597
return context
9698

9799

100+
class DateRangeChoices(models.TextChoices):
101+
LAST_7_DAYS = "last_7_days", "Last 7 Days"
102+
LAST_30_DAYS = "last_30_days", "Last 30 Days"
103+
LAST_3_MONTHS = "last_3_months", "Last 3 Months"
104+
LAST_6_MONTHS = "last_6_months", "Last 6 Months"
105+
THIS_YEAR = "this_year", "This Year"
106+
CUSTOM = "custom", "Custom Date Range"
107+
108+
98109
class NimbusExperimentFilter(django_filters.FilterSet):
99110
sort = django_filters.ChoiceFilter(
100111
method="filter_sort",
@@ -424,10 +435,25 @@ class NimbusExperimentsHomeFilter(django_filters.FilterSet):
424435
attrs={"title": "All Features"},
425436
),
426437
)
438+
date_range = django_filters.ChoiceFilter(
439+
choices=DateRangeChoices.choices,
440+
method="filter_by_date_range",
441+
required=False,
442+
)
427443

428444
class Meta:
429445
model = NimbusExperiment
430-
fields = ["my_deliveries_status", "sort", "status"]
446+
fields = [
447+
"my_deliveries_status",
448+
"sort",
449+
"status",
450+
"type",
451+
"qa_status",
452+
"channel",
453+
"application",
454+
"feature_configs",
455+
"date_range",
456+
]
431457

432458
def __init__(self, *args, **kwargs):
433459
super().__init__(*args, **kwargs)
@@ -522,3 +548,54 @@ def filter_feature_configs(self, queryset, name, values):
522548
if not values:
523549
return queryset
524550
return queryset.filter(feature_configs__in=values).distinct()
551+
552+
def filter_by_date_range(self, queryset, name, value):
553+
today = date.today()
554+
555+
match value:
556+
case DateRangeChoices.LAST_7_DAYS:
557+
start_date = today - timedelta(days=7)
558+
return queryset.filter(_start_date__gte=start_date)
559+
case DateRangeChoices.LAST_30_DAYS:
560+
start_date = today - timedelta(days=30)
561+
return queryset.filter(_start_date__gte=start_date)
562+
case DateRangeChoices.LAST_3_MONTHS:
563+
start_date = today - timedelta(days=90)
564+
return queryset.filter(_start_date__gte=start_date)
565+
case DateRangeChoices.LAST_6_MONTHS:
566+
start_date = today - timedelta(days=180)
567+
return queryset.filter(_start_date__gte=start_date)
568+
case DateRangeChoices.THIS_YEAR:
569+
start_date = date(today.year, 1, 1)
570+
return queryset.filter(_start_date__gte=start_date)
571+
case DateRangeChoices.CUSTOM:
572+
# Handle custom date range - can be start only, end only, or both
573+
query = Q()
574+
start_date_value = self.data.get("start_date")
575+
end_date_value = self.data.get("end_date")
576+
577+
# Apply start date filter if provided
578+
if start_date_value:
579+
try:
580+
parsed_start_date = datetime.strptime(
581+
start_date_value, "%Y-%m-%d"
582+
).date()
583+
query &= Q(_start_date__gte=parsed_start_date)
584+
except (ValueError, TypeError, AttributeError):
585+
pass
586+
587+
# Apply end date filter if provided
588+
if end_date_value:
589+
try:
590+
parsed_end_date = datetime.strptime(
591+
end_date_value, "%Y-%m-%d"
592+
).date()
593+
query &= Q(_computed_end_date__lte=parsed_end_date)
594+
except (ValueError, TypeError, AttributeError):
595+
pass
596+
597+
# Only apply filter if we have at least one valid date
598+
if start_date_value or end_date_value:
599+
return queryset.filter(query)
600+
601+
return queryset
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
{% load nimbus_extras %}
2+
3+
<div class="dropdown dropup date-filter ms-1 position-static">
4+
<button class="btn btn-sm p-0 border-0 bg-transparent position-relative"
5+
type="button"
6+
data-bs-toggle="dropdown"
7+
aria-expanded="false"
8+
id="dateFilterDropdown{{ dropdown_id }}"
9+
title="Filter by Date">
10+
<i class="fa-solid fa-filter text-body"></i>
11+
{% if form.date_range.value and form.date_range.value != "" %}
12+
<span class="badge bg-primary"
13+
style="position: absolute;
14+
top: -5px;
15+
right: -5px;
16+
font-size: 0.6em">1</span>
17+
{% elif selected_values and selected_values != "" and selected_values != None %}
18+
<span class="badge bg-primary"
19+
style="position: absolute;
20+
top: -5px;
21+
right: -5px;
22+
font-size: 0.6em">1</span>
23+
{% elif request.GET.start_date or request.GET.end_date %}
24+
<span class="badge bg-primary"
25+
style="position: absolute;
26+
top: -5px;
27+
right: -5px;
28+
font-size: 0.6em">1</span>
29+
{% endif %}
30+
</button>
31+
<div class="dropdown-menu p-2 shadow-sm border rounded-2"
32+
style="min-width: 280px;
33+
max-height: 50vh;
34+
overflow: auto">
35+
<strong class="dropdown-header">Filter by Date</strong>
36+
<div class="m-0 date-filter">
37+
{% for choice_value, choice_label in choices %}
38+
{% if choice_value not in "custom" %}
39+
<div class="form-check" id="filter-choice-date_range-{{ choice_value }}">
40+
<input class="form-check-input"
41+
type="radio"
42+
name="date_range"
43+
value="{{ choice_value }}"
44+
id="date_range-{{ choice_value }}"
45+
{% if choice_value == form.date_range.value %}checked{% endif %}
46+
hx-get="{{ url }}"
47+
hx-target="{{ hx_target }}"
48+
hx-select="{{ hx_select }}"
49+
hx-swap="outerHTML"
50+
hx-trigger="change"
51+
hx-include="input:checked, select"
52+
hx-vals='{"date_range": "{{ choice_value }}"}'>
53+
<label class="form-check-label" for="date_range-{{ choice_value }}">{{ choice_label }}</label>
54+
</div>
55+
{% endif %}
56+
{% endfor %}
57+
<hr class="my-2">
58+
<div class="form-check">
59+
<input class="form-check-input"
60+
type="radio"
61+
name="date_range"
62+
value="custom"
63+
id="date_range-custom"
64+
{% if form.date_range.value == "custom" or request.GET.start_date or request.GET.end_date %}checked{% endif %}
65+
hx-get="{{ url }}"
66+
hx-target="{{ hx_target }}"
67+
hx-select="{{ hx_select }}"
68+
hx-swap="outerHTML"
69+
hx-trigger="change"
70+
hx-include="input:checked, select, input[name='start_date'], input[name='end_date']"
71+
hx-vals='{"date_range": "custom"}'>
72+
<label class="form-check-label w-100" for="date_range-custom">
73+
<div class="d-flex flex-column">
74+
<span class="mb-2">Custom Date Range:</span>
75+
<div class="d-flex align-items-center mb-2">
76+
<label class="me-2" style="min-width: 80px;">Start Date:</label>
77+
<input type="date"
78+
name="start_date"
79+
class="form-control form-control-sm"
80+
style="width: 150px"
81+
value="{{ request.GET.start_date|default:'' }}"
82+
min="2000-01-01"
83+
onclick="document.getElementById('date_range-custom').checked = true;"
84+
hx-get="{{ url }}"
85+
hx-target="{{ hx_target }}"
86+
hx-select="{{ hx_select }}"
87+
hx-swap="outerHTML"
88+
hx-trigger="change delay:500ms"
89+
hx-include="input:checked, select, input[name='start_date'], input[name='end_date']"
90+
hx-vals='{"date_range": "custom"}'>
91+
</div>
92+
<div class="d-flex align-items-center">
93+
<label class="me-2" style="min-width: 80px;">End Date:</label>
94+
<input type="date"
95+
name="end_date"
96+
class="form-control form-control-sm"
97+
style="width: 150px"
98+
value="{{ request.GET.end_date|default:'' }}"
99+
min="2000-01-01"
100+
onclick="document.getElementById('date_range-custom').checked = true;"
101+
hx-get="{{ url }}"
102+
hx-target="{{ hx_target }}"
103+
hx-select="{{ hx_select }}"
104+
hx-swap="outerHTML"
105+
hx-trigger="change delay:500ms"
106+
hx-include="input:checked, select, input[name='start_date'], input[name='end_date']"
107+
hx-vals='{"date_range": "custom"}'>
108+
</div>
109+
</div>
110+
</label>
111+
</div>
112+
</div>
113+
</div>
114+
</div>

experimenter/experimenter/nimbus_ui/templates/common/home_sortable_header.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
hx-target="{{ hx_target }}"
88
hx-select="{{ hx_select }}"
99
hx-swap="outerHTML"
10-
hx-include=".status-filter input[name='status']:checked, .type-filter input[name='type']:checked, .qa-filter input[name='qa_status']:checked, .channel-filter input[name='channel']:checked, .application-filter input[name='application']:checked, .feature-filter input[name='feature_configs']:checked" {# keep current filters when sorting #}
10+
hx-include=".status-filter input[name='status']:checked, .type-filter input[name='type']:checked, .qa-filter input[name='qa_status']:checked, .channel-filter input[name='channel']:checked, .application-filter input[name='application']:checked, .feature-filter input[name='feature_configs']:checked, .date-filter input[name='date_range']:checked" {# keep current filters when sorting #}
1111
class="d-inline-block m-0">
1212
{# Preserve ALL existing filters except "sort" (we set it explicitly) #}
1313
{% for form_field in form %}
@@ -64,6 +64,11 @@
6464
{% if field == "application" %}
6565
{% include "common/filter_dropdown.html" with filter_name="application" filter_label="Application" choices=form.fields.application.choices selected_values=form.application.value icon_filter="application_icon_info" url=url hx_target=hx_target hx_select=hx_select current_sort=current_sort form=form dropdown_id=forloop.counter0 %}
6666

67+
{% endif %}
68+
{# ------------------------ FILTER FORM (Date Range) ------------------------ #}
69+
{% if field == "_start_date" %}
70+
{% include "common/date_filter_dropdown.html" with choices=form.fields.date_range.choices selected_values=form.date_range.value url=url hx_target=hx_target hx_select=hx_select current_sort=current_sort form=form dropdown_id=forloop.counter0 %}
71+
6772
{% endif %}
6873
{# ------------------------ FILTER FORM (Features) ------------------------ #}
6974
{% if field == "feature_configs__slug" %}

0 commit comments

Comments
 (0)