Skip to content

Commit

Permalink
Added Enterprise Data Exports Report
Browse files Browse the repository at this point in the history
  • Loading branch information
mjriley committed Dec 2, 2024
1 parent 9331fb1 commit 9fd706e
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 1 deletion.
2 changes: 2 additions & 0 deletions corehq/apps/enterprise/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
MobileUserResource,
ODataFeedResource,
WebUserResource,
DataExportReportResource
)

v1_api = Api(api_name='v1')
Expand All @@ -14,3 +15,4 @@
v1_api.register(MobileUserResource())
v1_api.register(FormSubmissionResource())
v1_api.register(ODataFeedResource())
v1_api.register(DataExportReportResource())
30 changes: 30 additions & 0 deletions corehq/apps/enterprise/api/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,3 +388,33 @@ def dehydrate(self, bundle):

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')
75 changes: 75 additions & 0 deletions corehq/apps/enterprise/enterprise.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
from couchforms.analytics import get_last_form_submission_received
from dimagi.utils.dates import DateSpan

from corehq.apps.users.models import WebUser
from corehq.apps.export.dbaccessors import (
get_brief_exports,
is_standard,
is_daily_saved_export,
is_excel_integration
)
from corehq.apps.enterprise.exceptions import EnterpriseReportError, TooMuchRequestedDataError
from corehq.apps.enterprise.iterators import raise_after_max_elements
from corehq.apps.accounting.models import BillingAccount
Expand All @@ -34,6 +41,7 @@ class EnterpriseReport:
MOBILE_USERS = 'mobile_users'
FORM_SUBMISSIONS = 'form_submissions'
ODATA_FEEDS = 'odata_feeds'
DATA_EXPORTS = 'data_exports'

DATE_ROW_FORMAT = '%Y/%m/%d %H:%M:%S'

Expand Down Expand Up @@ -67,6 +75,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)

if report:
report.slug = slug
Expand Down Expand Up @@ -383,3 +393,68 @@ def _get_individual_export_rows(self, exports, export_line_counts):
)

return rows


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

def __init__(self, account, couch_user):
super().__init__(account, couch_user)

@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))
1 change: 1 addition & 0 deletions corehq/apps/enterprise/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def enterprise_dashboard(request, domain):
EnterpriseReport.MOBILE_USERS,
EnterpriseReport.FORM_SUBMISSIONS,
EnterpriseReport.ODATA_FEEDS,
EnterpriseReport.DATA_EXPORTS,
)],
'current_page': {
'page_name': _('Enterprise Dashboard'),
Expand Down
27 changes: 26 additions & 1 deletion corehq/apps/export/dbaccessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from dimagi.utils.parsing import json_format_datetime

from corehq.util.test_utils import unit_testing_only
from couchexport.models import IntegrationFormat


def get_latest_case_export_schema(domain, case_type):
Expand Down Expand Up @@ -202,6 +203,30 @@ 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']
and not IntegrationFormat.is_integration_format(export['export_format']))


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


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


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 +245,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 9fd706e

Please sign in to comment.