Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions corehq/apps/enterprise/api/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from tastypie.api import Api

from corehq.apps.enterprise.api.resources import (
CaseManagementResource,
CommCareVersionComplianceResource,
DomainResource,
FormSubmissionResource,
Expand All @@ -19,6 +20,7 @@
v1_api.register(MobileUserResource())
v1_api.register(FormSubmissionResource())
v1_api.register(ODataFeedResource())
v1_api.register(CaseManagementResource())
v1_api.register(DataExportReportResource())
v1_api.register(CommCareVersionComplianceResource())
v1_api.register(SMSResource())
Expand Down
30 changes: 22 additions & 8 deletions corehq/apps/enterprise/api/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,28 @@ def get_primary_keys(self):
return ('form_id', 'submitted',)


class CaseManagementResource(ODataEnterpriseReportResource):
domain = fields.CharField()
num_applications = fields.IntegerField()
num_surveys_only = fields.IntegerField()
num_cases_only = fields.IntegerField()
num_mixed = fields.IntegerField()

REPORT_SLUG = EnterpriseReport.CASE_MANAGEMENT

def dehydrate(self, bundle):
bundle.data['domain'] = bundle.obj[0]
bundle.data['num_applications'] = bundle.obj[1]
bundle.data['num_surveys_only'] = bundle.obj[2]
bundle.data['num_cases_only'] = bundle.obj[3]
bundle.data['num_mixed'] = bundle.obj[4]

return bundle

def get_primary_keys(self):
return ('domain',)


class DataExportReportResource(ODataEnterpriseReportResource):
domain = fields.CharField()
name = fields.CharField()
Expand All @@ -423,14 +445,6 @@ class DataExportReportResource(ODataEnterpriseReportResource):

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]
Expand Down
64 changes: 64 additions & 0 deletions corehq/apps/enterprise/enterprise.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
)
from corehq.apps.enterprise.iterators import raise_after_max_elements
from corehq.apps.es import forms as form_es
from corehq.apps.es import filters
from corehq.apps.es.apps import AppES
from corehq.apps.es.users import UserES
from corehq.apps.export.dbaccessors import ODataExportFetcher
from corehq.apps.reports.util import (
Expand All @@ -52,6 +54,7 @@ class EnterpriseReport(ABC):
MOBILE_USERS = 'mobile_users'
FORM_SUBMISSIONS = 'form_submissions'
ODATA_FEEDS = 'odata_feeds'
CASE_MANAGEMENT = 'case_management'
DATA_EXPORTS = 'data_exports'
COMMCARE_VERSION_COMPLIANCE = 'commcare_version_compliance'
SMS = 'sms'
Expand Down Expand Up @@ -100,6 +103,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.CASE_MANAGEMENT:
report = EnterpriseCaseManagementReport(account, couch_user, **kwargs)
elif slug == cls.DATA_EXPORTS:
report = EnterpriseDataExportReport(account, couch_user, **kwargs)
elif slug == cls.COMMCARE_VERSION_COMPLIANCE:
Expand Down Expand Up @@ -410,6 +415,65 @@ def rows_for_domain(self, domain_obj):
return rows


class EnterpriseCaseManagementReport(EnterpriseReport):
title = gettext_lazy('Case Management')
total_description = gettext_lazy('% of Domains using Case Management')

@property
def headers(self):
return [_('Project Space'), _('# Applications'), _('# Surveys Only'), _('# Cases Only'), _('# Mixed')]

def rows_for_domain(self, domain_obj):
app_query = self.app_query(domain_obj.name)
app_count = app_query.count()

if app_count == 0:
survey_only_count = 0
case_only_count = 0
mixed_count = 0
else:
has_surveys = filters.nested('modules', filters.empty('modules.case_type.exact'))
has_cases = filters.nested('modules', filters.non_null('modules.case_type.exact'))

survey_only_count = app_query.filter(filters.AND(has_surveys, filters.NOT(has_cases))).count()
case_only_count = app_query.filter(filters.AND(has_cases, filters.NOT(has_surveys))).count()
mixed_count = app_query.filter(filters.AND(has_surveys, has_cases)).count()

return [[domain_obj.name, app_count, survey_only_count, case_only_count, mixed_count],]

@property
def total(self):
num_domains_with_apps = 0
num_domains_using_case_management = 0

for domain_obj in self.domains():
(app_count, uses_case_management) = self.total_for_domain(domain_obj)
if app_count > 0:
if uses_case_management:
num_domains_using_case_management += 1
num_domains_with_apps += 1

return _format_percentage_for_enterprise_tile(num_domains_using_case_management, num_domains_with_apps)

def total_for_domain(self, domain_obj):
app_query = self.app_query(domain_obj.name)
app_count = app_query.count()
if app_count > 0:
has_cases = filters.nested('modules', filters.non_null('modules.case_type.exact'))
uses_case_management = app_query.filter(has_cases).count() > 0
else:
uses_case_management = False

return [app_count, uses_case_management]

def app_query(self, domain):
return (
AppES().domain(domain)
.filter(filters.term('doc_type', 'Application'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this to make sure we don't get LinkedApplications or RemoteApps?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I think this was probably influenced pretty heavily by talking with Ethan, so I'm not sure what my thinking was at the time (this was likely code I used directly from him). That said, When I run the query without the Application filter, I receive DeleteFormRecord, DeleteModuleRecord, and DeleteApplicationRecord in addition to Application -- so it seems AppES holds more than current application information

.is_build(False)
)


class EnterpriseDataExportReport(EnterpriseReport):
title = gettext_lazy('Data Exports')
total_description = gettext_lazy('# of Exports')
Expand Down
4 changes: 3 additions & 1 deletion corehq/apps/enterprise/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ 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, EnterpriseReport.DATA_EXPORTS)]},
for slug in (EnterpriseReport.ODATA_FEEDS,
EnterpriseReport.DATA_EXPORTS,
EnterpriseReport.CASE_MANAGEMENT,)]},
],
'uses_date_range': [EnterpriseReport.FORM_SUBMISSIONS, EnterpriseReport.SMS],
'metric_type': 'Platform Overview',
Expand Down
Loading