This commit is contained in:
Rachid Mrad 2024-03-01 18:32:45 -05:00
parent 34769f2d01
commit 717f71f038
No known key found for this signature in database
6 changed files with 425 additions and 318 deletions

View file

@ -5,14 +5,14 @@ import datetime
from django import forms
from django.db.models import Avg, F, Value, CharField, Q
from django.db.models.functions import Concat, Coalesce
from django.http import HttpResponse, HttpResponseRedirect
from django.http import HttpResponseRedirect
from django.shortcuts import redirect, render
from django_fsm import get_available_FIELD_transitions
from django.contrib import admin, messages
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
from django.urls import path, reverse
from django.urls import reverse
from dateutil.relativedelta import relativedelta # type: ignore
from epplibwrapper.errors import ErrorCode, RegistryError
from registrar.models import Contact, Domain, DomainApplication, DraftDomain, User, Website
@ -367,9 +367,7 @@ def analytics(request):
thirty_days_ago = datetime.datetime.today() - datetime.timedelta(days=30)
last_30_days_applications = models.DomainApplication.objects.filter(
created_at__gt=thirty_days_ago
)
last_30_days_applications = models.DomainApplication.objects.filter(created_at__gt=thirty_days_ago)
last_30_days_approved_applications = models.DomainApplication.objects.filter(
created_at__gt=thirty_days_ago, status=DomainApplication.ApplicationStatus.APPROVED
)
@ -433,8 +431,6 @@ def analytics(request):
}
deleted_domains_sliced_at_end_date = csv_export.get_sliced_domains(filter_deleted_domains_end_date)
# Created and Submitted requests
filter_requests_start_date = {
"created_at__lte": start_date_formatted,
@ -466,26 +462,23 @@ def analytics(request):
last_30_days_applications=last_30_days_applications.count(),
last_30_days_approved_applications=last_30_days_approved_applications.count(),
average_application_approval_time_last_30_days=avg_approval_time,
managed_domains_sliced_at_start_date=managed_domains_sliced_at_start_date,
unmanaged_domains_sliced_at_start_date=unmanaged_domains_sliced_at_start_date,
managed_domains_sliced_at_end_date=managed_domains_sliced_at_end_date,
unmanaged_domains_sliced_at_end_date=unmanaged_domains_sliced_at_end_date,
ready_domains_sliced_at_start_date=ready_domains_sliced_at_start_date,
deleted_domains_sliced_at_start_date=deleted_domains_sliced_at_start_date,
ready_domains_sliced_at_end_date=ready_domains_sliced_at_end_date,
deleted_domains_sliced_at_end_date=deleted_domains_sliced_at_end_date,
requests_sliced_at_start_date=requests_sliced_at_start_date,
submitted_requests_sliced_at_start_date=submitted_requests_sliced_at_start_date,
requests_sliced_at_end_date=requests_sliced_at_end_date,
submitted_requests_sliced_at_end_date=submitted_requests_sliced_at_end_date,
),
)
return render(request, "admin/analytics.html", context)
class MyUserAdmin(BaseUserAdmin):
"""Custom user admin class to use our inlines."""

View file

@ -10,7 +10,15 @@ from django.views.generic import RedirectView
from registrar import views
from registrar.admin import analytics
from registrar.views.admin_views import ExportDataDomainsGrowth, ExportDataFederal, ExportDataFull, ExportDataManagedDomains, ExportDataRequestsGrowth, ExportDataType, ExportDataUnmanagedDomains
from registrar.views.admin_views import (
ExportDataDomainsGrowth,
ExportDataFederal,
ExportDataFull,
ExportDataManagedDomains,
ExportDataRequestsGrowth,
ExportDataType,
ExportDataUnmanagedDomains,
)
from registrar.views.application import Step
from registrar.views.utility import always_404
@ -91,7 +99,6 @@ urlpatterns = [
admin.site.admin_view(analytics),
name="analytics",
),
path("admin/", admin.site.urls),
path(
"application/<id>/edit/",

View file

@ -27,7 +27,7 @@ def handle_profile(sender, instance, **kwargs):
last_name = getattr(instance, "last_name", "")
email = getattr(instance, "email", "")
phone = getattr(instance, "phone", "")
logger.info(f'in handle_profile first {instance}')
logger.info(f"in handle_profile first {instance}")
is_new_user = kwargs.get("created", False)
@ -37,7 +37,7 @@ def handle_profile(sender, instance, **kwargs):
contacts = Contact.objects.filter(user=instance)
if len(contacts) == 0: # no matching contact
logger.info(f'inside no matching contacts for first {first_name} last {last_name} email {email}')
logger.info(f"inside no matching contacts for first {first_name} last {last_name} email {email}")
Contact.objects.create(
user=instance,
first_name=first_name,

View file

@ -22,10 +22,8 @@
</ul>
</div>
</div>
</div>
<div class="tablet:grid-col-6 margin-top-2">
<div class="module height-full">
<h2>Current domains</h2>
<div class="padding-top-2 padding-x-2">
@ -54,8 +52,9 @@
</ul>
</div>
</div>
</div>
</div>
</div></div>
<div class="grid-row grid-gap-2 margin-top-2">
<div class="grid-col">
<div class="module">
@ -78,7 +77,6 @@
<input type="date" id="end" name="end" value="2023-12-01" min="2023-12-01" />
</div>
</div>
<ul class="usa-button-group">
<li class="usa-button-group__item">
<button class="button exportLink" data-export-url="{% url 'export_domains_growth' %}" type="button">
@ -166,6 +164,5 @@
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -22,6 +22,7 @@ def write_header(writer, columns):
"""
writer.writerow(columns)
def get_domain_infos(filter_condition, sort_fields):
domain_infos = (
DomainInformation.objects.select_related("domain", "authorizing_official")
@ -42,6 +43,7 @@ def get_domain_infos(filter_condition, sort_fields):
)
return domain_infos_cleaned
def parse_row(columns, domain_info: DomainInformation, security_emails_dict=None, get_domain_managers=False):
"""Given a set of columns, generate a new row from cleaned column data"""
@ -102,6 +104,7 @@ def parse_row(columns, domain_info: DomainInformation, security_emails_dict=None
row = [FIELDS.get(column, "") for column in columns]
return row
def _get_security_emails(sec_contact_ids):
"""
Retrieve security contact emails for the given security contact IDs.
@ -123,6 +126,7 @@ def _get_security_emails(sec_contact_ids):
return security_emails_dict
def update_columns_with_domain_managers(columns, max_dm_count):
"""
Update the columns list to include "Domain manager email {#}" headers
@ -131,6 +135,7 @@ def update_columns_with_domain_managers(columns, max_dm_count):
for i in range(1, max_dm_count + 1):
columns.append(f"Domain manager email {i}")
def write_csv(
writer,
columns,
@ -180,19 +185,17 @@ def write_csv(
writer.writerows(rows)
def get_requests(filter_condition, sort_fields):
requests = (
DomainApplication.objects.all()
.filter(**filter_condition)
.order_by(*sort_fields)
)
requests = DomainApplication.objects.all().filter(**filter_condition).order_by(*sort_fields)
return requests
def parse_request_row(columns, request: DomainApplication):
"""Given a set of columns, generate a new row from cleaned column data"""
requested_domain_name = 'No requested domain'
requested_domain_name = "No requested domain"
# Domain should never be none when parsing this information
if request.requested_domain is not None:
@ -224,6 +227,7 @@ def parse_request_row(columns, request: DomainApplication):
row = [FIELDS.get(column, "") for column in columns]
return row
def write_requests_csv(
writer,
columns,
@ -231,8 +235,7 @@ def write_requests_csv(
filter_condition,
should_write_header=True,
):
"""
"""
""" """
all_requetsts = get_requests(filter_condition, sort_fields)
@ -257,6 +260,7 @@ def write_requests_csv(
writer.writerows(rows)
def export_data_type_to_csv(csv_file):
"""All domains report with extra columns"""
@ -293,6 +297,7 @@ def export_data_type_to_csv(csv_file):
}
write_csv(writer, columns, sort_fields, filter_condition, get_domain_managers=True, should_write_header=True)
def export_data_full_to_csv(csv_file):
"""All domains report"""
@ -323,6 +328,7 @@ def export_data_full_to_csv(csv_file):
}
write_csv(writer, columns, sort_fields, filter_condition, get_domain_managers=False, should_write_header=True)
def export_data_federal_to_csv(csv_file):
"""Federal domains report"""
@ -354,20 +360,25 @@ def export_data_federal_to_csv(csv_file):
}
write_csv(writer, columns, sort_fields, filter_condition, get_domain_managers=False, should_write_header=True)
def get_default_start_date():
# Default to a date that's prior to our first deployment
return timezone.make_aware(datetime(2023, 11, 1))
def get_default_end_date():
# Default to now()
return timezone.now()
def format_start_date(start_date):
return timezone.make_aware(datetime.strptime(start_date, "%Y-%m-%d")) if start_date else get_default_start_date()
def format_end_date(end_date):
return timezone.make_aware(datetime.strptime(end_date, "%Y-%m-%d")) if end_date else get_default_end_date()
def export_data_domain_growth_to_csv(csv_file, start_date, end_date):
"""
Growth report:
@ -425,15 +436,17 @@ def export_data_domain_growth_to_csv(csv_file, start_date, end_date):
should_write_header=False,
)
def get_sliced_domains(filter_condition):
"""Get fitered domains counts sliced by org type and election office.
"""
"""Get fitered domains counts sliced by org type and election office."""
domains = DomainInformation.objects.all().filter(**filter_condition)
domains_count = domains.count()
federal = domains.filter(organization_type=DomainApplication.OrganizationChoices.FEDERAL).count()
interstate = domains.filter(organization_type=DomainApplication.OrganizationChoices.INTERSTATE).count()
state_or_territory = domains.filter(organization_type=DomainApplication.OrganizationChoices.STATE_OR_TERRITORY).count()
state_or_territory = domains.filter(
organization_type=DomainApplication.OrganizationChoices.STATE_OR_TERRITORY
).count()
tribal = domains.filter(organization_type=DomainApplication.OrganizationChoices.TRIBAL).count()
county = domains.filter(organization_type=DomainApplication.OrganizationChoices.COUNTY).count()
city = domains.filter(organization_type=DomainApplication.OrganizationChoices.CITY).count()
@ -441,7 +454,8 @@ def get_sliced_domains(filter_condition):
school_district = domains.filter(organization_type=DomainApplication.OrganizationChoices.SCHOOL_DISTRICT).count()
election_board = domains.filter(is_election_board=True).count()
return [domains_count,
return [
domains_count,
federal,
interstate,
state_or_territory,
@ -450,17 +464,20 @@ def get_sliced_domains(filter_condition):
city,
special_district,
school_district,
election_board]
election_board,
]
def get_sliced_requests(filter_condition):
"""Get fitered requests counts sliced by org type and election office.
"""
"""Get fitered requests counts sliced by org type and election office."""
requests = DomainApplication.objects.all().filter(**filter_condition)
requests_count = requests.count()
federal = requests.filter(organization_type=DomainApplication.OrganizationChoices.FEDERAL).count()
interstate = requests.filter(organization_type=DomainApplication.OrganizationChoices.INTERSTATE).count()
state_or_territory = requests.filter(organization_type=DomainApplication.OrganizationChoices.STATE_OR_TERRITORY).count()
state_or_territory = requests.filter(
organization_type=DomainApplication.OrganizationChoices.STATE_OR_TERRITORY
).count()
tribal = requests.filter(organization_type=DomainApplication.OrganizationChoices.TRIBAL).count()
county = requests.filter(organization_type=DomainApplication.OrganizationChoices.COUNTY).count()
city = requests.filter(organization_type=DomainApplication.OrganizationChoices.CITY).count()
@ -468,7 +485,8 @@ def get_sliced_requests(filter_condition):
school_district = requests.filter(organization_type=DomainApplication.OrganizationChoices.SCHOOL_DISTRICT).count()
election_board = requests.filter(is_election_board=True).count()
return [requests_count,
return [
requests_count,
federal,
interstate,
state_or_territory,
@ -477,11 +495,12 @@ def get_sliced_requests(filter_condition):
city,
special_district,
school_district,
election_board]
election_board,
]
def export_data_managed_domains_to_csv(csv_file, start_date, end_date):
"""Get domains have domain managers for two different dates.
"""
"""Get domains have domain managers for two different dates."""
start_date_formatted = format_start_date(start_date)
end_date_formatted = format_end_date(end_date)
@ -501,11 +520,31 @@ def export_data_managed_domains_to_csv(csv_file, start_date, end_date):
managed_domains_sliced_at_start_date = get_sliced_domains(filter_managed_domains_start_date)
writer.writerow(["MANAGED DOMAINS COUNTS AT SRAT DATE"])
writer.writerow(["Total", "Federal", "Interstate", "State or territory", "Tribal", "County", "City", "Special district", "School district", "Election office"])
writer.writerow(
[
"Total",
"Federal",
"Interstate",
"State or territory",
"Tribal",
"County",
"City",
"Special district",
"School district",
"Election office",
]
)
writer.writerow(managed_domains_sliced_at_start_date)
writer.writerow([])
write_csv(writer, columns, sort_fields, filter_managed_domains_start_date, get_domain_managers=True, should_write_header=True)
write_csv(
writer,
columns,
sort_fields,
filter_managed_domains_start_date,
get_domain_managers=True,
should_write_header=True,
)
writer.writerow([])
filter_managed_domains_end_date = {
@ -515,15 +554,35 @@ def export_data_managed_domains_to_csv(csv_file, start_date, end_date):
managed_domains_sliced_at_end_date = get_sliced_domains(filter_managed_domains_end_date)
writer.writerow(["MANAGED DOMAINS COUNTS AT END DATE"])
writer.writerow(["Total", "Federal", "Interstate", "State or territory", "Tribal", "County", "City", "Special district", "School district", "Election office"])
writer.writerow(
[
"Total",
"Federal",
"Interstate",
"State or territory",
"Tribal",
"County",
"City",
"Special district",
"School district",
"Election office",
]
)
writer.writerow(managed_domains_sliced_at_end_date)
writer.writerow([])
write_csv(writer, columns, sort_fields, filter_managed_domains_end_date, get_domain_managers=True, should_write_header=True)
write_csv(
writer,
columns,
sort_fields,
filter_managed_domains_end_date,
get_domain_managers=True,
should_write_header=True,
)
def export_data_unmanaged_domains_to_csv(csv_file, start_date, end_date):
""" Get domains that do not have domain managers for two different dates.
"""
"""Get domains that do not have domain managers for two different dates."""
start_date_formatted = format_start_date(start_date)
end_date_formatted = format_end_date(end_date)
@ -543,11 +602,31 @@ def export_data_unmanaged_domains_to_csv(csv_file, start_date, end_date):
unmanaged_domains_sliced_at_start_date = get_sliced_domains(filter_unmanaged_domains_start_date)
writer.writerow(["UNMANAGED DOMAINS AT START DATE"])
writer.writerow(["Total", "Federal", "Interstate", "State or territory", "Tribal", "County", "City", "Special district", "School district", "Election office"])
writer.writerow(
[
"Total",
"Federal",
"Interstate",
"State or territory",
"Tribal",
"County",
"City",
"Special district",
"School district",
"Election office",
]
)
writer.writerow(unmanaged_domains_sliced_at_start_date)
writer.writerow([])
write_csv(writer, columns, sort_fields, filter_unmanaged_domains_start_date, get_domain_managers=True, should_write_header=True)
write_csv(
writer,
columns,
sort_fields,
filter_unmanaged_domains_start_date,
get_domain_managers=True,
should_write_header=True,
)
writer.writerow([])
filter_unmanaged_domains_end_date = {
@ -557,15 +636,35 @@ def export_data_unmanaged_domains_to_csv(csv_file, start_date, end_date):
unmanaged_domains_sliced_at_end_date = get_sliced_domains(filter_unmanaged_domains_end_date)
writer.writerow(["UNMANAGED DOMAINS AT END DATE"])
writer.writerow(["Total", "Federal", "Interstate", "State or territory", "Tribal", "County", "City", "Special district", "School district", "Election office"])
writer.writerow(
[
"Total",
"Federal",
"Interstate",
"State or territory",
"Tribal",
"County",
"City",
"Special district",
"School district",
"Election office",
]
)
writer.writerow(unmanaged_domains_sliced_at_end_date)
writer.writerow([])
write_csv(writer, columns, sort_fields, filter_unmanaged_domains_end_date, get_domain_managers=True, should_write_header=True)
write_csv(
writer,
columns,
sort_fields,
filter_unmanaged_domains_end_date,
get_domain_managers=True,
should_write_header=True,
)
def export_data_requests_growth_to_csv(csv_file, start_date, end_date):
"""
"""
""" """
start_date_formatted = format_start_date(start_date)
end_date_formatted = format_end_date(end_date)

View file

@ -9,6 +9,7 @@ import logging
logger = logging.getLogger(__name__)
class ExportDataType(View):
def get(self, request, *args, **kwargs):
# match the CSV example with all the fields
@ -17,6 +18,7 @@ class ExportDataType(View):
csv_export.export_data_type_to_csv(response)
return response
class ExportDataFull(View):
def get(self, request, *args, **kwargs):
# Smaller export based on 1
@ -25,6 +27,7 @@ class ExportDataFull(View):
csv_export.export_data_full_to_csv(response)
return response
class ExportDataFederal(View):
def get(self, request, *args, **kwargs):
# Federal only
@ -33,6 +36,7 @@ class ExportDataFederal(View):
csv_export.export_data_federal_to_csv(response)
return response
class ExportDataDomainsGrowth(View):
def get(self, request, *args, **kwargs):
# Get start_date and end_date from the request's GET parameters
@ -48,6 +52,7 @@ class ExportDataDomainsGrowth(View):
return response
class ExportDataRequestsGrowth(View):
def get(self, request, *args, **kwargs):
# Get start_date and end_date from the request's GET parameters
@ -63,6 +68,7 @@ class ExportDataRequestsGrowth(View):
return response
class ExportDataManagedDomains(View):
def get(self, request, *args, **kwargs):
# Get start_date and end_date from the request's GET parameters
@ -70,11 +76,14 @@ class ExportDataManagedDomains(View):
start_date = request.GET.get("start_date", "")
end_date = request.GET.get("end_date", "")
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = f'attachment; filename="managed-vs-unamanaged-domains-{start_date}-to-{end_date}.csv"'
response["Content-Disposition"] = (
f'attachment; filename="managed-vs-unamanaged-domains-{start_date}-to-{end_date}.csv"'
)
csv_export.export_data_managed_domains_to_csv(response, start_date, end_date)
return response
class ExportDataUnmanagedDomains(View):
def get(self, request, *args, **kwargs):
# Get start_date and end_date from the request's GET parameters
@ -82,7 +91,9 @@ class ExportDataUnmanagedDomains(View):
start_date = request.GET.get("start_date", "")
end_date = request.GET.get("end_date", "")
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = f'attachment; filename="managed-vs-unamanaged-domains-{start_date}-to-{end_date}.csv"'
response["Content-Disposition"] = (
f'attachment; filename="managed-vs-unamanaged-domains-{start_date}-to-{end_date}.csv"'
)
csv_export.export_data_unmanaged_domains_to_csv(response, start_date, end_date)
return response