From a48a4060a67b603fcbad1713779ad8f4b9cf9b04 Mon Sep 17 00:00:00 2001 From: Ali Date: Tue, 14 Jul 2020 14:38:41 -0700 Subject: [PATCH 01/10] Added new page for the deploy history report based on the existing user list report --- corehq/apps/hqadmin/reports.py | 42 ++++++++++++++++++++++++++++++++++ corehq/reports.py | 2 ++ corehq/tabs/tabclasses.py | 3 +++ 3 files changed, 47 insertions(+) diff --git a/corehq/apps/hqadmin/reports.py b/corehq/apps/hqadmin/reports.py index 68da664a38c5..6ddbc256d4c1 100644 --- a/corehq/apps/hqadmin/reports.py +++ b/corehq/apps/hqadmin/reports.py @@ -22,6 +22,7 @@ from corehq.apps.sms.mixin import apply_leniency from corehq.apps.sms.models import PhoneNumber from corehq.const import SERVER_DATETIME_FORMAT +from corehq.apps.hqadmin.models import HqDeploy class AdminReport(GenericTabularReport): @@ -266,3 +267,44 @@ def _format_date(date): if date: return parse(date).strftime(SERVER_DATETIME_FORMAT) return "---" + + +class DeployHistoryReport(GetParamsMixin, AdminReport): + base_template = 'reports/base_template.html' + + slug = 'deploy_history_report' + name = ugettext_lazy("Deploy History Report") + + fields = [ + 'corehq.apps.reports.filters.simple.SimpleSearch', + ] + emailable = False + exportable = False + ajax_pagination = True + default_rows = 10 + + @property + def headers(self): + return DataTablesHeader( + DataTablesColumn(_("Date")), + DataTablesColumn(_("User")), + DataTablesColumn(_("Diff URL")), + ) + + @property + def rows(self): + deploy_list = HqDeploy.objects.filter()[:10] + for deploy in deploy_list: + yield [ + deploy.date, + deploy.user, + deploy.diff_url, + ] + + @property + def total_records(self): + return self.default_rows + + @cached_property + def _user_lookup_url(self): + return reverse('web_deploy_lookup') diff --git a/corehq/reports.py b/corehq/reports.py index 9e3d5c892261..f206db835fc4 100644 --- a/corehq/reports.py +++ b/corehq/reports.py @@ -10,6 +10,7 @@ DeviceLogSoftAssertReport, UserAuditReport, UserListReport, + DeployHistoryReport, ) from corehq.apps.linked_domain.views import DomainLinkHistoryReport from corehq.apps.reports.standard import ( @@ -339,6 +340,7 @@ def get_report_builder_count(domain): DeviceLogSoftAssertReport, AdminPhoneNumberReport, UserAuditReport, + DeployHistoryReport, )), ) diff --git a/corehq/tabs/tabclasses.py b/corehq/tabs/tabclasses.py index 134f870569c1..6b6b79e64dcb 100644 --- a/corehq/tabs/tabclasses.py +++ b/corehq/tabs/tabclasses.py @@ -37,6 +37,7 @@ DeviceLogSoftAssertReport, UserAuditReport, UserListReport, + DeployHistoryReport, ) from corehq.apps.hqadmin.views.system import GlobalThresholds from corehq.apps.hqwebapp.models import GaTracker @@ -2213,6 +2214,8 @@ def sidebar_items(self): (_('Administrative Reports'), [ {'title': _('User List'), 'url': UserListReport.get_url()}, + {'title': _('Deploy History'), + 'url': DeployHistoryReport.get_url()}, {'title': _('Download Malt table'), 'url': reverse('download_malt')}, {'title': _('Download Global Impact Report'), From 7001e721730105d000443672387ee132216f185c Mon Sep 17 00:00:00 2001 From: Ali Date: Wed, 15 Jul 2020 06:47:50 -0700 Subject: [PATCH 02/10] Formatted datetime object --- corehq/apps/hqadmin/reports.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/corehq/apps/hqadmin/reports.py b/corehq/apps/hqadmin/reports.py index 6ddbc256d4c1..542dd23cc6b6 100644 --- a/corehq/apps/hqadmin/reports.py +++ b/corehq/apps/hqadmin/reports.py @@ -275,6 +275,8 @@ class DeployHistoryReport(GetParamsMixin, AdminReport): slug = 'deploy_history_report' name = ugettext_lazy("Deploy History Report") + # can remove simple search in seperate commit + # search should accept git ref and show the deploy that contains that ref. More involved than simpleSearch fields = [ 'corehq.apps.reports.filters.simple.SimpleSearch', ] @@ -296,7 +298,7 @@ def rows(self): deploy_list = HqDeploy.objects.filter()[:10] for deploy in deploy_list: yield [ - deploy.date, + self._format_date(deploy.date), deploy.user, deploy.diff_url, ] @@ -308,3 +310,10 @@ def total_records(self): @cached_property def _user_lookup_url(self): return reverse('web_deploy_lookup') + + @staticmethod + def _format_date(date): + #find time relative to now -> last_deploy.date|naturaltime + if date: + return date.strftime(SERVER_DATETIME_FORMAT) + return "---" From 797467b3e9e38f01284fec5fa898d058f8eba787 Mon Sep 17 00:00:00 2001 From: Ali Date: Wed, 15 Jul 2020 06:49:48 -0700 Subject: [PATCH 03/10] Removed simpleSearch functionality --- corehq/apps/hqadmin/reports.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/corehq/apps/hqadmin/reports.py b/corehq/apps/hqadmin/reports.py index 542dd23cc6b6..ce10112b13a4 100644 --- a/corehq/apps/hqadmin/reports.py +++ b/corehq/apps/hqadmin/reports.py @@ -274,12 +274,9 @@ class DeployHistoryReport(GetParamsMixin, AdminReport): slug = 'deploy_history_report' name = ugettext_lazy("Deploy History Report") - - # can remove simple search in seperate commit + # search should accept git ref and show the deploy that contains that ref. More involved than simpleSearch - fields = [ - 'corehq.apps.reports.filters.simple.SimpleSearch', - ] + emailable = False exportable = False ajax_pagination = True From 66ee9787952db6d2501b96873a440ee00ee53762 Mon Sep 17 00:00:00 2001 From: Ali Date: Wed, 15 Jul 2020 10:23:51 -0700 Subject: [PATCH 04/10] removed total_records --- corehq/apps/hqadmin/reports.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/corehq/apps/hqadmin/reports.py b/corehq/apps/hqadmin/reports.py index ce10112b13a4..5e267b8e8eba 100644 --- a/corehq/apps/hqadmin/reports.py +++ b/corehq/apps/hqadmin/reports.py @@ -274,7 +274,7 @@ class DeployHistoryReport(GetParamsMixin, AdminReport): slug = 'deploy_history_report' name = ugettext_lazy("Deploy History Report") - + # search should accept git ref and show the deploy that contains that ref. More involved than simpleSearch emailable = False @@ -300,10 +300,6 @@ def rows(self): deploy.diff_url, ] - @property - def total_records(self): - return self.default_rows - @cached_property def _user_lookup_url(self): return reverse('web_deploy_lookup') From 949ae017bf0bd79255f9f3b0ed4df5f1ec2121e0 Mon Sep 17 00:00:00 2001 From: Ali Date: Wed, 15 Jul 2020 10:58:10 -0700 Subject: [PATCH 05/10] Added hyperlink to diff url --- corehq/apps/hqadmin/reports.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/corehq/apps/hqadmin/reports.py b/corehq/apps/hqadmin/reports.py index 5e267b8e8eba..c67fea4225cf 100644 --- a/corehq/apps/hqadmin/reports.py +++ b/corehq/apps/hqadmin/reports.py @@ -292,12 +292,12 @@ def headers(self): @property def rows(self): - deploy_list = HqDeploy.objects.filter()[:10] + deploy_list = HqDeploy.objects.filter() for deploy in deploy_list: yield [ self._format_date(deploy.date), deploy.user, - deploy.diff_url, + self._hyperlink_diff_url(deploy.diff_url), ] @cached_property @@ -310,3 +310,9 @@ def _format_date(date): if date: return date.strftime(SERVER_DATETIME_FORMAT) return "---" + + def _hyperlink_diff_url(self, diff_url): + hyperlink_diff_url = 'Diff with previous' + hyperlink_diff_url = hyperlink_diff_url.format(link=diff_url) + return hyperlink_diff_url + From 0012f974d2ba46cc70f17e0da01c487bec796254 Mon Sep 17 00:00:00 2001 From: Ali Date: Wed, 15 Jul 2020 12:42:55 -0700 Subject: [PATCH 06/10] Added relative time date --- corehq/apps/hqadmin/reports.py | 49 ++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/corehq/apps/hqadmin/reports.py b/corehq/apps/hqadmin/reports.py index c67fea4225cf..264382f5e3b8 100644 --- a/corehq/apps/hqadmin/reports.py +++ b/corehq/apps/hqadmin/reports.py @@ -3,6 +3,7 @@ from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy, ugettext_noop +from datetime import datetime as dt from dateutil.parser import parse from memoized import memoized @@ -78,19 +79,22 @@ def rows(self): def _filter_logs(self): logs = DeviceReportEntry.objects.filter( - date__range=[self.datespan.startdate_param_utc, self.datespan.enddate_param_utc] + date__range=[self.datespan.startdate_param_utc, + self.datespan.enddate_param_utc] ).filter(type='soft-assert') if self.selected_domain is not None: logs = logs.filter(domain__exact=self.selected_domain) if self.selected_commcare_version is not None: - logs = logs.filter(app_version__contains='"{}"'.format(self.selected_commcare_version)) + logs = logs.filter(app_version__contains='"{}"'.format( + self.selected_commcare_version)) return logs def _create_row(self, log, *args, **kwargs): - row = super(DeviceLogSoftAssertReport, self)._create_row(log, *args, **kwargs) + row = super(DeviceLogSoftAssertReport, self)._create_row( + log, *args, **kwargs) row.append(log.domain) return row @@ -135,7 +139,8 @@ def _get_rows(self, paginate=True, link_user=True): return if paginate and self.pagination: - data = data[self.pagination.start:self.pagination.start + self.pagination.count] + data = data[ + self.pagination.start:self.pagination.start + self.pagination.count] for number in data: yield self._fmt_row(number, owner_cache, link_user) @@ -275,8 +280,6 @@ class DeployHistoryReport(GetParamsMixin, AdminReport): slug = 'deploy_history_report' name = ugettext_lazy("Deploy History Report") - # search should accept git ref and show the deploy that contains that ref. More involved than simpleSearch - emailable = False exportable = False ajax_pagination = True @@ -285,9 +288,9 @@ class DeployHistoryReport(GetParamsMixin, AdminReport): @property def headers(self): return DataTablesHeader( - DataTablesColumn(_("Date")), - DataTablesColumn(_("User")), - DataTablesColumn(_("Diff URL")), + DataTablesColumn(_("Date"), sortable=False), + DataTablesColumn(_("User"), sortable=False), + DataTablesColumn(_("Diff URL"), sortable=False), ) @property @@ -304,15 +307,33 @@ def rows(self): def _user_lookup_url(self): return reverse('web_deploy_lookup') - @staticmethod - def _format_date(date): - #find time relative to now -> last_deploy.date|naturaltime + def _format_date(self, date): + raw_time_since_deploy = dt.now() - date + delta_dict = self._strfdelta(raw_time_since_deploy) + + if delta_dict['days'] != 0: + delta_str = "{days} day(s), {hours} hour(s) ago".format(**delta_dict) + elif delta_dict['hours'] != 0: + delta_str = "{hours} hour(s), {minutes} minute(s) ago".format( + **delta_dict) + else: + delta_str = "{minutes} minute(s), {seconds} second(s) ago".format( + **delta_dict) + + ret_str = '
{delta}
{actual_date}
' if date: - return date.strftime(SERVER_DATETIME_FORMAT) + return ret_str.format(delta=delta_str, actual_date=date.strftime(SERVER_DATETIME_FORMAT)) return "---" + def _strfdelta(self, tdelta): + # datetime.timedelta object cannot use 'strftime', use custom fn: + relative_time_formatted = {"days": tdelta.days} + relative_time_formatted["hours"], rem = divmod(tdelta.seconds, 3600) + relative_time_formatted["minutes"], relative_time_formatted[ + "seconds"] = divmod(rem, 60) + return relative_time_formatted + def _hyperlink_diff_url(self, diff_url): hyperlink_diff_url = 'Diff with previous' hyperlink_diff_url = hyperlink_diff_url.format(link=diff_url) return hyperlink_diff_url - From d4c842857d8e05a6239c931cec3e5fa04a13115a Mon Sep 17 00:00:00 2001 From: Ali Date: Wed, 15 Jul 2020 13:07:56 -0700 Subject: [PATCH 07/10] Consolidated lines --- corehq/apps/hqadmin/reports.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/corehq/apps/hqadmin/reports.py b/corehq/apps/hqadmin/reports.py index 264382f5e3b8..8055051ba8eb 100644 --- a/corehq/apps/hqadmin/reports.py +++ b/corehq/apps/hqadmin/reports.py @@ -282,13 +282,13 @@ class DeployHistoryReport(GetParamsMixin, AdminReport): emailable = False exportable = False - ajax_pagination = True + ajax_pagination = False default_rows = 10 @property def headers(self): return DataTablesHeader( - DataTablesColumn(_("Date"), sortable=False), + DataTablesColumn(_("Date")), DataTablesColumn(_("User"), sortable=False), DataTablesColumn(_("Diff URL"), sortable=False), ) @@ -308,20 +308,20 @@ def _user_lookup_url(self): return reverse('web_deploy_lookup') def _format_date(self, date): - raw_time_since_deploy = dt.now() - date - delta_dict = self._strfdelta(raw_time_since_deploy) - - if delta_dict['days'] != 0: - delta_str = "{days} day(s), {hours} hour(s) ago".format(**delta_dict) - elif delta_dict['hours'] != 0: - delta_str = "{hours} hour(s), {minutes} minute(s) ago".format( - **delta_dict) - else: - delta_str = "{minutes} minute(s), {seconds} second(s) ago".format( - **delta_dict) - - ret_str = '
{delta}
{actual_date}
' if date: + raw_time_since_deploy = dt.now() - date + delta_dict = self._strfdelta(raw_time_since_deploy) + + if delta_dict['days'] != 0: + delta_str = "{days} day(s), {hours} hour(s) ago".format(**delta_dict) + elif delta_dict['hours'] != 0: + delta_str = "{hours} hour(s), {minutes} minute(s) ago".format( + **delta_dict) + else: + delta_str = "{minutes} minute(s), {seconds} second(s) ago".format( + **delta_dict) + + ret_str = '
{delta}
{actual_date}
' return ret_str.format(delta=delta_str, actual_date=date.strftime(SERVER_DATETIME_FORMAT)) return "---" @@ -334,6 +334,4 @@ def _strfdelta(self, tdelta): return relative_time_formatted def _hyperlink_diff_url(self, diff_url): - hyperlink_diff_url = 'Diff with previous' - hyperlink_diff_url = hyperlink_diff_url.format(link=diff_url) - return hyperlink_diff_url + return 'Diff with previous'.format(link=diff_url) From 9ebaac4eae4a52558674f8b5882b7772dc8222bb Mon Sep 17 00:00:00 2001 From: Ali Date: Wed, 15 Jul 2020 13:29:03 -0700 Subject: [PATCH 08/10] Added pagination --- corehq/apps/hqadmin/reports.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/corehq/apps/hqadmin/reports.py b/corehq/apps/hqadmin/reports.py index 8055051ba8eb..5fd084981c1c 100644 --- a/corehq/apps/hqadmin/reports.py +++ b/corehq/apps/hqadmin/reports.py @@ -73,7 +73,8 @@ def rows(self): logs = self._filter_logs() rows = self._create_rows( logs, - range=slice(self.pagination.start, self.pagination.start + self.pagination.count) + range=slice(self.pagination.start, + self.pagination.start + self.pagination.count) ) return rows @@ -282,27 +283,33 @@ class DeployHistoryReport(GetParamsMixin, AdminReport): emailable = False exportable = False - ajax_pagination = False + ajax_pagination = True default_rows = 10 @property def headers(self): return DataTablesHeader( - DataTablesColumn(_("Date")), + DataTablesColumn(_("Date"), sortable=False), DataTablesColumn(_("User"), sortable=False), DataTablesColumn(_("Diff URL"), sortable=False), ) @property def rows(self): - deploy_list = HqDeploy.objects.filter() - for deploy in deploy_list: + deploy_list = HqDeploy.objects.all() + start = self.pagination.start + end = self.pagination.start + self.pagination.count + for deploy in deploy_list[start:end]: yield [ self._format_date(deploy.date), deploy.user, self._hyperlink_diff_url(deploy.diff_url), ] + @property + def total_records(self): + return HqDeploy.objects.count() + @cached_property def _user_lookup_url(self): return reverse('web_deploy_lookup') @@ -313,7 +320,8 @@ def _format_date(self, date): delta_dict = self._strfdelta(raw_time_since_deploy) if delta_dict['days'] != 0: - delta_str = "{days} day(s), {hours} hour(s) ago".format(**delta_dict) + delta_str = "{days} day(s), {hours} hour(s) ago".format( + **delta_dict) elif delta_dict['hours'] != 0: delta_str = "{hours} hour(s), {minutes} minute(s) ago".format( **delta_dict) From 624612fb178713f58a65ea1035cfa08b002258ac Mon Sep 17 00:00:00 2001 From: Ali Date: Thu, 23 Jul 2020 13:10:39 -0700 Subject: [PATCH 09/10] Address PR comments - change to naturaltime, remove unnecessary functions, use f-strings --- corehq/apps/hqadmin/reports.py | 31 +++---------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/corehq/apps/hqadmin/reports.py b/corehq/apps/hqadmin/reports.py index 5fd084981c1c..8602502368bd 100644 --- a/corehq/apps/hqadmin/reports.py +++ b/corehq/apps/hqadmin/reports.py @@ -2,6 +2,7 @@ from django.utils.functional import cached_property from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy, ugettext_noop +from django.contrib.humanize.templatetags.humanize import naturaltime from datetime import datetime as dt from dateutil.parser import parse @@ -310,36 +311,10 @@ def rows(self): def total_records(self): return HqDeploy.objects.count() - @cached_property - def _user_lookup_url(self): - return reverse('web_deploy_lookup') - def _format_date(self, date): if date: - raw_time_since_deploy = dt.now() - date - delta_dict = self._strfdelta(raw_time_since_deploy) - - if delta_dict['days'] != 0: - delta_str = "{days} day(s), {hours} hour(s) ago".format( - **delta_dict) - elif delta_dict['hours'] != 0: - delta_str = "{hours} hour(s), {minutes} minute(s) ago".format( - **delta_dict) - else: - delta_str = "{minutes} minute(s), {seconds} second(s) ago".format( - **delta_dict) - - ret_str = '
{delta}
{actual_date}
' - return ret_str.format(delta=delta_str, actual_date=date.strftime(SERVER_DATETIME_FORMAT)) + return f'
{naturaltime(date)}
{date.strftime(SERVER_DATETIME_FORMAT)}
' return "---" - def _strfdelta(self, tdelta): - # datetime.timedelta object cannot use 'strftime', use custom fn: - relative_time_formatted = {"days": tdelta.days} - relative_time_formatted["hours"], rem = divmod(tdelta.seconds, 3600) - relative_time_formatted["minutes"], relative_time_formatted[ - "seconds"] = divmod(rem, 60) - return relative_time_formatted - def _hyperlink_diff_url(self, diff_url): - return 'Diff with previous'.format(link=diff_url) + return f'Diff with previous' From 26b4a54b688216f25a78e3fe87ce465592424aff Mon Sep 17 00:00:00 2001 From: Ali Date: Fri, 24 Jul 2020 09:14:43 -0700 Subject: [PATCH 10/10] remove unused import --- corehq/apps/hqadmin/reports.py | 1 - 1 file changed, 1 deletion(-) diff --git a/corehq/apps/hqadmin/reports.py b/corehq/apps/hqadmin/reports.py index 8602502368bd..d0931c2c05df 100644 --- a/corehq/apps/hqadmin/reports.py +++ b/corehq/apps/hqadmin/reports.py @@ -4,7 +4,6 @@ from django.utils.translation import ugettext_lazy, ugettext_noop from django.contrib.humanize.templatetags.humanize import naturaltime -from datetime import datetime as dt from dateutil.parser import parse from memoized import memoized