Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Application Version Compliance Report #35567

Merged
merged 36 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b71b73b
Add App Version Compliance Report
jingcheng16 Dec 24, 2024
0fcd272
app_id can be None or empty string
jingcheng16 Dec 25, 2024
1e27613
is_out_of_date expect string
jingcheng16 Dec 25, 2024
64ccd26
Exclude apps that has been deleted
jingcheng16 Dec 25, 2024
9c8d16a
Exclude deleted app before wrap
jingcheng16 Dec 26, 2024
f94b4b3
Extract necessary attributes directly instead of wrapping the raw doc…
jingcheng16 Dec 26, 2024
553d901
Rename wrapped_builds_cache to build_by_build_id
jingcheng16 Dec 26, 2024
d414a5c
Use built_on when no last_released
jingcheng16 Dec 26, 2024
1d64d78
Cache application name by id
jingcheng16 Dec 26, 2024
cbf4831
Don't query all domains object
jingcheng16 Dec 27, 2024
5175df2
Refactor: reorder funcion
jingcheng16 Dec 27, 2024
106d01d
Avoid wraping Application object
jingcheng16 Dec 27, 2024
1859605
Avoid wrap LastBuild
jingcheng16 Dec 27, 2024
0e7f9bb
Only fetch all builds if necessary
jingcheng16 Dec 30, 2024
ea9809a
Implement total calculation
jingcheng16 Dec 30, 2024
a0f29a2
Refactor: DRY
jingcheng16 Dec 30, 2024
848cf09
Rename variables and show up-to-date percentage instead of out-of-dat…
jingcheng16 Dec 30, 2024
b024957
text update and reformat
jingcheng16 Jan 1, 2025
716a387
Rename column
jingcheng16 Jan 1, 2025
990c12a
Add ApplicationVersionCompliance api
jingcheng16 Jan 1, 2025
25e5e52
Refactor: Move get_app_builds inside get_latest_build_version_at_time
jingcheng16 Jan 2, 2025
c446ad1
Refactor: rename app_id to app_ids
jingcheng16 Jan 3, 2025
980b616
Clear the cache for initial 10 builds
jingcheng16 Jan 6, 2025
3a271f1
Refactor: renaming variable and text update
jingcheng16 Jan 7, 2025
08443df
Remove Initial Query Limit since it makes the logic complicated
jingcheng16 Jan 7, 2025
4d009d5
Return a dictionary in _get_user_builds
jingcheng16 Jan 8, 2025
12a4ff6
Always show '--' when display metric
jingcheng16 Jan 9, 2025
5bb7b5a
Refactor: renaming parameter so the function is self-explanatory and …
jingcheng16 Jan 9, 2025
936c43e
Refactor: rename variable to make it more self-explanatory
jingcheng16 Jan 9, 2025
8a4accd
Refactor: extract the logic in _find_latest_build_version_from_builds…
jingcheng16 Jan 9, 2025
c79f198
Refactor: check the cache first and return immediately
jingcheng16 Jan 9, 2025
7d9d565
Merge branch 'master' into jc/app_version_compliance_report
jingcheng16 Jan 13, 2025
3a0985a
Add comment
jingcheng16 Jan 15, 2025
a680d0d
Merge branch 'master' into jc/app_version_compliance_report
jingcheng16 Jan 21, 2025
df144d4
Refactor: rename to improve readability
jingcheng16 Jan 22, 2025
95fb0d8
Update the descriptor
jingcheng16 Jan 22, 2025
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
92 changes: 90 additions & 2 deletions corehq/apps/enterprise/enterprise.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
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
from corehq.apps.app_manager.models import Application, SavedAppBuild
from corehq.apps.builds.utils import get_latest_version_at_time, is_out_of_date
from corehq.apps.builds.models import CommCareBuildConfig
from corehq.apps.domain.calculations import sms_in_last
Expand All @@ -25,7 +26,7 @@
TooMuchRequestedDataError,
)
from corehq.apps.enterprise.iterators import raise_after_max_elements
from corehq.apps.es import forms as form_es
from corehq.apps.es import AppES, forms as form_es
from corehq.apps.es.users import UserES
from corehq.apps.export.dbaccessors import ODataExportFetcher
from corehq.apps.reports.util import (
Expand All @@ -37,7 +38,7 @@
get_mobile_user_count,
get_web_user_count,
)
from corehq.apps.users.models import CouchUser, HQApiKey, Invitation, WebUser
from corehq.apps.users.models import CouchUser, HQApiKey, Invitation, LastBuild, WebUser


class EnterpriseReport(ABC):
Expand All @@ -50,6 +51,7 @@ class EnterpriseReport(ABC):
SMS = 'sms'
API_USAGE = 'api_usage'
TWO_FACTOR_AUTH = '2fa'
APP_VERSION_COMPLIANCE = 'app_version_compliance'

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

Expand Down Expand Up @@ -101,6 +103,8 @@ def create(cls, slug, account_id, couch_user, **kwargs):
report = EnterpriseAPIReport(account, couch_user, **kwargs)
elif slug == cls.TWO_FACTOR_AUTH:
report = Enterprise2FAReport(account, couch_user, **kwargs)
elif slug == cls.APP_VERSION_COMPLIANCE:
nospame marked this conversation as resolved.
Show resolved Hide resolved
report = EnterpriseAppVersionComplianceReport(account, couch_user, **kwargs)

if report:
report.slug = slug
Expand Down Expand Up @@ -635,3 +639,87 @@ def rows_for_domain(self, domain_obj):
if domain_obj.two_factor_auth:
return []
return [(domain_obj.name,)]


class EnterpriseAppVersionComplianceReport(EnterpriseReport):
title = gettext_lazy('Application Version Compliance')
total_description = gettext_lazy('% of Applications Up-to-Date Across All Mobile Workers')
nospame marked this conversation as resolved.
Show resolved Hide resolved

def __init__(self, account, couch_user):
super().__init__(account, couch_user)
self.builds_by_app_id = {}
self.wrapped_builds_cache = {}

def get_app_builds(self, domain, app_id):
if app_id not in self.builds_by_app_id:
app_es = (
AppES()
.domain(domain)
.is_build()
.app_id(app_id)
.sort('version', desc=True)
.is_released()
)
self.builds_by_app_id[app_id] = app_es.run().hits

return self.builds_by_app_id[app_id]

@property
def headers(self):
return [_('Mobile Worker'), _('Project Space'), _('Application'),
_('Latest Version Available at Submission'),
_('Version in Use'), _('Last Used [UTC]')]
nospame marked this conversation as resolved.
Show resolved Hide resolved

def rows_for_domain(self, domain_obj):
rows = []

user_query = (UserES()
.domain(domain_obj.name)
.mobile_users()
.source([
'username',
'reporting_metadata.last_builds',
]))
for user in user_query.run().hits:
last_builds = user.get('reporting_metadata', {}).get('last_builds', [])
nospame marked this conversation as resolved.
Show resolved Hide resolved
for build in last_builds:
build = LastBuild.wrap(build)
if build.build_version:
all_builds = self.get_app_builds(domain_obj.name, build.app_id)
latest_version = self.get_latest_build_version_at_time(all_builds,
build.build_version_date)
if is_out_of_date(build.build_version, latest_version):
rows.append([
user['username'],
domain_obj.name,
Application.get(build.app_id).name,
latest_version,
build.build_version,
self.format_date(build.build_version_date),
])

return rows

def total_for_domain(self, domain_obj):
return 0

def get_latest_build_version_at_time(self, all_builds, time):
"""
Get the latest build version at the time

:param all_builds: List of raw build documents sorted by version in descending order
:param time: The date of the build version to compare against
nospame marked this conversation as resolved.
Show resolved Hide resolved
:return: The latest build version available at the given date
"""

for build_doc in all_builds:
build_id = build_doc['_id']
if build_id in self.wrapped_builds_cache:
build = self.wrapped_builds_cache[build_id]
else:
build = SavedAppBuild.wrap(build_doc)
self.wrapped_builds_cache[build_id] = build

if build.last_released <= time:
return build.version
return None
1 change: 1 addition & 0 deletions corehq/apps/enterprise/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def platform_overview(request, domain):
EnterpriseReport.ODATA_FEEDS,
EnterpriseReport.COMMCARE_VERSION_COMPLIANCE,
EnterpriseReport.SMS,
EnterpriseReport.APP_VERSION_COMPLIANCE,
)],
'uses_date_range': [EnterpriseReport.FORM_SUBMISSIONS, EnterpriseReport.SMS],
'metric_type': 'Platform Overview',
Expand Down