Skip to content

Commit

Permalink
Merge pull request #35462 from dimagi/mjr/data-exports-report
Browse files Browse the repository at this point in the history
Added Enterprise Data Exports Report
  • Loading branch information
mjriley authored Jan 16, 2025
2 parents 6836391 + 632f7d7 commit dd99fca
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 3 deletions.
2 changes: 2 additions & 0 deletions corehq/apps/enterprise/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
MobileUserResource,
ODataFeedResource,
WebUserResource,
DataExportReportResource,
SMSResource,
APIUsageResource,
TwoFactorAuthResource,
Expand All @@ -18,6 +19,7 @@
v1_api.register(MobileUserResource())
v1_api.register(FormSubmissionResource())
v1_api.register(ODataFeedResource())
v1_api.register(DataExportReportResource())
v1_api.register(CommCareVersionComplianceResource())
v1_api.register(SMSResource())
v1_api.register(APIUsageResource())
Expand Down
31 changes: 30 additions & 1 deletion corehq/apps/enterprise/api/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from django.utils.translation import gettext as _

from dateutil import tz
from datetime import timezone
from tastypie import fields, http
from tastypie.exceptions import ImmediateHttpResponse

Expand Down Expand Up @@ -415,6 +414,36 @@ def get_primary_keys(self):
return ('form_id', 'submitted',)


class DataExportReportResource(ODataEnterpriseReportResource):
domain = fields.CharField()
name = fields.CharField()
export_type = fields.CharField()
export_subtype = fields.CharField()
owner = fields.CharField()

REPORT_SLUG = EnterpriseReport.DATA_EXPORTS

def get_report_task(self, request):
account = BillingAccount.get_account_by_domain(request.domain)
return generate_enterprise_report.s(
self.REPORT_SLUG,
account.id,
request.couch_user.username
)

def dehydrate(self, bundle):
bundle.data['domain'] = bundle.obj[0]
bundle.data['name'] = bundle.obj[1]
bundle.data['export_type'] = bundle.obj[2]
bundle.data['export_subtype'] = bundle.obj[3]
bundle.data['owner'] = bundle.obj[4]

return bundle

def get_primary_keys(self):
return ('domain', 'export_type', 'export_subtype', 'name')


class TwoFactorAuthResource(ODataEnterpriseReportResource):
domain_without_2fa = fields.CharField()

Expand Down
72 changes: 72 additions & 0 deletions corehq/apps/enterprise/enterprise.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
from couchforms.analytics import get_last_form_submission_received
from dimagi.utils.dates import DateSpan

from corehq.apps.export.dbaccessors import (
get_brief_exports,
is_standard,
is_daily_saved_export,
is_excel_integration
)
from corehq.apps.accounting.models import BillingAccount
from corehq.apps.accounting.utils import get_default_domain_url
from corehq.apps.app_manager.dbaccessors import get_brief_apps_in_domain
Expand Down Expand Up @@ -46,6 +52,7 @@ class EnterpriseReport(ABC):
MOBILE_USERS = 'mobile_users'
FORM_SUBMISSIONS = 'form_submissions'
ODATA_FEEDS = 'odata_feeds'
DATA_EXPORTS = 'data_exports'
COMMCARE_VERSION_COMPLIANCE = 'commcare_version_compliance'
SMS = 'sms'
API_USAGE = 'api_usage'
Expand Down Expand Up @@ -93,6 +100,8 @@ def create(cls, slug, account_id, couch_user, **kwargs):
report = EnterpriseFormReport(account, couch_user, **kwargs)
elif slug == cls.ODATA_FEEDS:
report = EnterpriseODataReport(account, couch_user, **kwargs)
elif slug == cls.DATA_EXPORTS:
report = EnterpriseDataExportReport(account, couch_user, **kwargs)
elif slug == cls.COMMCARE_VERSION_COMPLIANCE:
report = EnterpriseCommCareVersionReport(account, couch_user, **kwargs)
elif slug == cls.SMS:
Expand Down Expand Up @@ -401,6 +410,69 @@ def rows_for_domain(self, domain_obj):
return rows


class EnterpriseDataExportReport(EnterpriseReport):
title = gettext_lazy('Data Exports')
total_description = gettext_lazy('# of Exports')

@property
def headers(self):
return [
_('Project Space'),
_('Name'),
_('Type'),
_('SubType'),
_('Created By'),
]

def type_lookup(self, doc_type):
from corehq.apps.export.models.new import FormExportInstance, CaseExportInstance
if doc_type == FormExportInstance.__name__:
return _('Form')
elif doc_type == CaseExportInstance.__name__:
return _('Case')
else:
return _('Unknown')

SUBTYPE_MAP = {
is_standard: gettext_lazy('Standard'),
is_daily_saved_export: gettext_lazy('Daily Saved Export'),
is_excel_integration: gettext_lazy('Excel Integration'),
}

def subtype_lookup(self, export):
for (is_subtype_fn, subtype) in self.SUBTYPE_MAP.items():
if is_subtype_fn(export):
return subtype

return _('Unknown')

def user_lookup(self, owner_id):
if not owner_id:
return _('Unknown')

owner = WebUser.get_by_user_id(owner_id)
return owner.username

def get_exports(self, domain_obj):
valid_subtypes = self.SUBTYPE_MAP.values()
return [
export for export in get_brief_exports(domain_obj.name)
if self.subtype_lookup(export) in valid_subtypes
]

def rows_for_domain(self, domain_obj):
return [[
domain_obj.name,
export['name'],
self.type_lookup(export['doc_type']),
self.subtype_lookup(export),
self.user_lookup(export['owner_id']),
] for export in self.get_exports(domain_obj)]

def total_for_domain(self, domain_obj):
return len(self.get_exports(domain_obj))


class EnterpriseCommCareVersionReport(EnterpriseReport):
title = gettext_lazy('CommCare Version Compliance')
total_description = gettext_lazy('% of Mobile Workers on the Latest CommCare Version')
Expand Down
2 changes: 1 addition & 1 deletion corehq/apps/enterprise/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def platform_overview(request, domain):
EnterpriseReport.COMMCARE_VERSION_COMPLIANCE,)]},
{'name': _('Data Management & Export'),
'reports': [EnterpriseReport.create(slug, request.account.id, request.couch_user)
for slug in (EnterpriseReport.ODATA_FEEDS,)]},
for slug in (EnterpriseReport.ODATA_FEEDS, EnterpriseReport.DATA_EXPORTS)]},
],
'uses_date_range': [EnterpriseReport.FORM_SUBMISSIONS, EnterpriseReport.SMS],
'metric_type': 'Platform Overview',
Expand Down
23 changes: 22 additions & 1 deletion corehq/apps/export/dbaccessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,27 @@ def delete_all_export_instances():
safe_delete(db, doc_id)


def is_standard(export):
return (not export['is_daily_saved_export']
and not export['is_odata_config'])


def is_daily_saved_export(export):
return (export['is_daily_saved_export']
and not export['export_format'] == "html"
and not export['is_odata_config'])


def is_excel_integration(export):
return (export['is_daily_saved_export']
and export['export_format'] == "html"
and not export['is_odata_config'])


def is_odata_export(export):
return export['is_odata_config']


class ODataExportFetcher:
def get_export_count(self, domain):
return len(self._get_odata_exports(domain))
Expand All @@ -220,4 +241,4 @@ def get_exports(self, domain):

def _get_odata_exports(self, domain):
all_domain_exports = get_brief_exports(domain)
return [export for export in all_domain_exports if export['is_odata_config']]
return [export for export in all_domain_exports if is_odata_export(export)]

0 comments on commit dd99fca

Please sign in to comment.