Source code for cfme.intelligence.reports.reports

# -*- coding: utf-8 -*-
"""Page model for Cloud Intel / Reports / Reports"""
import attr

from cached_property import cached_property
from navmazing import NavigateToAttribute, NavigateToSibling
from widgetastic.exceptions import NoSuchElementException
from widgetastic.utils import ParametrizedLocator, VersionPick, Version
from widgetastic.widget import Text, Checkbox, View, ParametrizedView, Table as VanillaTable
from widgetastic_patternfly import Button, Input, BootstrapSelect, Tab, CandidateNotFound

from cfme.modeling.base import BaseCollection, BaseEntity
from cfme.utils import ParamClassName
from cfme.utils.appliance.implementations.ui import navigator, CFMENavigateStep, navigate_to
from cfme.utils.blockers import BZ
from cfme.utils.pretty import Pretty
from cfme.utils.timeutil import parsetime
from cfme.utils.update import Updateable
from cfme.utils.wait import wait_for
from widgetastic_manageiq import (PaginationPane, Table, ReportToolBarViewSelector,
                                  NonJSPaginationPane)
from widgetastic_manageiq.expression_editor import ExpressionEditor
from . import CloudIntelReportsView, ReportsMultiBoxSelect


[docs]class CustomReportFormCommon(CloudIntelReportsView): report_title = Text("#explorer_title_text") menu_name = Input("name") title = Input("title") base_report_on = BootstrapSelect("chosen_model") report_fields = ReportsMultiBoxSelect( move_into="Move selected fields down", move_from="Move selected fields up", available_items="available_fields", chosen_items="selected_fields" ) cancel_after = BootstrapSelect("chosen_queue_timeout") @View.nested class consolidation(Tab): # noqa column1 = BootstrapSelect("chosen_pivot1") column2 = BootstrapSelect("chosen_pivot2") column3 = BootstrapSelect("chosen_pivot3") @View.nested class formatting(Tab): # noqa page_size = BootstrapSelect("pdf_page_size") @View.nested class styling(Tab): # noqa pass @View.nested class filter(Tab): # noqa filter_show_costs = BootstrapSelect("cb_show_typ") filter_owner = BootstrapSelect("cb_owner_id") filter_provider = BootstrapSelect("cb_provider_id") filter_project = BootstrapSelect("cb_entity_id") filter_tag_cat = BootstrapSelect("cb_tag_cat") filter_tag_value = BootstrapSelect("cb_tag_value") interval = BootstrapSelect("cb_interval") interval_size = BootstrapSelect("cb_interval_size") interval_end = BootstrapSelect("cb_end_interval_offset") primary_filter = ExpressionEditor() secondary_filter = ExpressionEditor() @View.nested class summary(Tab): # noqa sort_by = BootstrapSelect("chosen_sort1") sort_order = BootstrapSelect("sort_order") show_breaks = BootstrapSelect("sort_group") sort_by_2 = BootstrapSelect("chosen_sort2") row_limit = BootstrapSelect("row_limit") @View.nested class charts(Tab): # noqa chart_type = BootstrapSelect("chosen_graph") chart_mode = BootstrapSelect("chart_mode") values_to_show = BootstrapSelect("chosen_count") sum_other_values = Checkbox("chosen_other") @View.nested class timeline(Tab): # noqa based_on = BootstrapSelect("chosen_tl") position = BootstrapSelect("chosen_position") cancel_button = Button("Cancel")
[docs]class ReportAddView(CustomReportFormCommon): add_button = Button("Add") @property def is_displayed(self): return ( self.in_intel_reports and self.reports.is_opened and self.report_title.text == "Adding a new Report" and self.reports.tree.currently_selected == ["All Reports"] )
[docs]class ReportEditView(CustomReportFormCommon): save_button = Button("Save") @property def is_displayed(self): return ( self.in_intel_reports and self.reports.is_opened and self.reports.tree.currently_selected == [ "All Reports", self.context["object"].company_name, "Custom", self.context["object"].menu_name ] and self.report_title.text == 'Editing Report "{}"'.format(self.context["object"].menu_name) )
[docs]class ReportDetailsView(CloudIntelReportsView): title = Text("#explorer_title_text") reload_button = Button( title=VersionPick({ Version.lowest(): 'Reload current display', '5.9': 'Refresh this page' }) ) paginator = PaginationPane() @View.nested class report_info(Tab): # noqa TAB_NAME = "Report Info" queue_button = Button("Queue") @View.nested class saved_reports(Tab): # noqa TAB_NAME = "Saved Reports" table = Table(".//div[@id='records_div' or @class='miq-data-table']/table") paginator = PaginationPane() @property def is_displayed(self): return ( self.in_intel_reports and self.reports.is_opened and self.report_info.is_active() and self.reports.tree.currently_selected == [ "All Reports", self.context["object"].type or self.context["object"].company_name, self.context["object"].subtype or "Custom", self.context["object"].menu_name ] and self.title.text == 'Report "{}"'.format(self.context["object"].menu_name) )
[docs]class SavedReportDetailsView(CloudIntelReportsView): title = Text("#explorer_title_text") table = VanillaTable(".//div[@id='report_html_div']/table") # PaginationPane() is not working on Report Details page paginator = View.nested(NonJSPaginationPane) view_selector = View.nested(ReportToolBarViewSelector) @ParametrizedView.nested class download(ParametrizedView): # noqa PARAMETERS = ("format", ) ALL_LINKS = ".//a[starts-with(@name, 'download_choice__render_report_')]" download_button = Button(title="Download") link = Text(ParametrizedLocator(".//a[normalize-space()={format|quote}]")) def __init__(self, *args, **kwargs): ParametrizedView.__init__(self, *args, **kwargs) self.download_button.click() self.link.click() @classmethod def all(cls, browser): return [(browser.text(e), ) for e in browser.elements(cls.ALL_LINKS)] @property def is_displayed(self): return ( self.in_intel_reports and self.reports.is_opened and self.reports.tree.currently_selected == [ "All Reports", self.context["object"].report.type or self.context["object"].report.company_name, self.context["object"].report.subtype or "Custom", self.context["object"].report.menu_name, self.context["object"].datetime_in_tree ] and self.title.text == 'Saved Report "{} - {}"'.format( self.context["object"].report.title, self.context["object"].queued_datetime_in_title ) )
[docs]class AllReportsView(CloudIntelReportsView): title = Text("#explorer_title_text") reports_table = VanillaTable(".//div[@id='report_list_div']/table") @property def is_displayed(self): return ( self.in_intel_reports and self.reports.is_opened and self.reports.tree.currently_selected == ["All Reports"] and self.title.text == "All Reports" and self.reports_table.is_displayed )
[docs]class AllCustomReportsView(CloudIntelReportsView): title = Text("#explorer_title_text") @property def is_displayed(self): return ( self.in_intel_reports and self.reports.is_opened and self.reports.tree.currently_selected == [ "All Reports", self.context["object"].company_name, "Custom" ] and self.title.text == "Custom Reports" )
[docs]@attr.s class Report(BaseEntity, Updateable): _param_name = ParamClassName('title') menu_name = attr.ib(default=None) title = attr.ib(default=None) company_name = attr.ib() type = attr.ib(default=None) subtype = attr.ib(default=None) base_report_on = attr.ib(default=None) report_fields = attr.ib(default=None) cancel_after = attr.ib(default=None) consolidation = attr.ib(default=None) formatting = attr.ib(default=None) styling = attr.ib(default=None) filter = attr.ib(default=None) filter_show_costs = attr.ib(default=None) filter_owner = attr.ib(default=None) filter_tag_cat = attr.ib(default=None) filter_tag_value = attr.ib(default=None) interval = attr.ib(default=None) interval_size = attr.ib(default=None) interval_end = attr.ib(default=None) sort = attr.ib(default=None) chart_type = attr.ib(default=None) top_values = attr.ib(default=None) sum_other = attr.ib(default=None) base_timeline_on = attr.ib(default=None) band_units = attr.ib(default=None) event_position = attr.ib(default=None) show_event_unit = attr.ib(default=None) show_event_count = attr.ib(default=None) summary = attr.ib(default=None) charts = attr.ib(default=None) timeline = attr.ib(default=None) is_candu = attr.ib(default=False) def __attrs_post_init__(self): self._collections = {'saved_reports': SavedReportsCollection}
[docs] @company_name.default def company_name_default(self): if self.appliance.version < "5.9": return "My Company (All EVM Groups)" else: return "My Company (All Groups)"
[docs] def update(self, updates): view = navigate_to(self, "Edit") changed = view.fill(updates) if changed: view.save_button.click() else: view.cancel_button.click() view = self.create_view(ReportDetailsView, override=updates) assert view.is_displayed view.flash.assert_no_error() if changed: view.flash.assert_message( 'Report "{}" was saved'.format(self.menu_name)) else: view.flash.assert_message( 'Edit of Report "{}" was cancelled by the user'.format(self.menu_name))
[docs] def delete(self, cancel=False): view = navigate_to(self, "Details") node = view.reports.tree.expand_path("All Reports", self.company_name, "Custom") custom_reports_number = len(view.reports.tree.child_items(node)) view.configuration.item_select("Delete this Report from the Database", handle_alert=not cancel) if cancel: assert view.is_displayed view.flash.assert_no_error() else: # This check is needed because after deleting the last custom report, # the whole "My Company (All EVM Groups)" branch in the tree will be removed. if custom_reports_number > 1: view = self.create_view(AllCustomReportsView) assert view.is_displayed view.flash.assert_no_error() if not BZ(1561779, forced_streams=['5.9', '5.8']).blocks: view.flash.assert_message( 'Report "{}": Delete successful'.format(self.menu_name))
@cached_property def saved_reports(self): return self.collections.saved_reports
[docs] def queue(self, wait_for_finish=False): view = navigate_to(self, "Details") view.report_info.queue_button.click() view.flash.assert_no_error() if wait_for_finish: # Get the queued_at value to always target the correct row queued_at = view.saved_reports.table[0]["Queued At"].text def _get_state(): row = view.saved_reports.table.row(queued_at=queued_at) status = row.status.text.strip().lower() assert status != "error" return status == "complete" wait_for( _get_state, delay=1, message="wait for report generation finished", fail_func=view.reload_button.click, num_sec=300, ) view.reload_button.click() first_row = view.saved_reports.table[0] saved_report = self.saved_reports.instantiate( first_row.run_at.text, first_row.queued_at.text, self.is_candu ) return saved_report
@property def exists(self): try: navigate_to(self, "Details") return True except CandidateNotFound: return False
[docs]@attr.s class ReportsCollection(BaseCollection): ENTITY = Report
[docs] def create(self, **values): view = navigate_to(self, "Add") view.fill(values) view.add_button.click() view = self.create_view(AllReportsView) assert view.is_displayed view.flash.assert_no_error() view.flash.assert_message('Report "{}" was added'.format(values["menu_name"])) return self.instantiate(**values)
[docs]@attr.s class SavedReport(Updateable, BaseEntity): """Custom Saved Report. Enables us to retrieve data from the table. Args: run_datetime: Datetime of "Run At" of the report. That's what :py:func:`queue` returns. queued_datetime: Datetime of "Queued At" of the report. candu: If it is a C&U report, in that case it uses a different table. """ run_datetime = attr.ib() queued_datetime = attr.ib() candu = attr.ib(default=False) @property def parent_obj(self): return self.parent.parent @property def report(self): return self.parent_obj @cached_property def queued_datetime_in_title(self): return parsetime.from_american_with_utc(self.queued_datetime).to_saved_report_title_format() @cached_property def datetime_in_tree(self): return parsetime.from_american_with_utc(self.run_datetime).to_iso_with_utc() @cached_property def data(self): """Retrieves data from the saved report. Returns: :py:class:`SavedReportData`. """ view = navigate_to(self, "Details") if 'No records found for this report' in view.flash.read(): # No data found return SavedReportData([], []) view.paginator.set_items_per_page(1000) try: headers = tuple([hdr.encode("utf-8") for hdr in view.table.headers]) body = [] for _ in view.paginator.pages(): for row in view.table.rows(): if not all([c[1].is_displayed for c in row]): # This is a temporary workaround for cases we have row span # greater that 1 column (e.g. in case of "Totals: ddd" column). # TODO: Support this functionality in widgetastic. Issue: # https://github.com/RedHatQE/widgetastic.core/issues/26 continue row_data = tuple([row[header].text.encode("utf-8") for header in headers]) body.append(row_data) except NoSuchElementException: # No data found return SavedReportData([], []) else: return SavedReportData(headers, body)
[docs] def download(self, extension): extensions_mapping = {"txt": "Text", "csv": "CSV", "pdf": "PDF"} view = navigate_to(self, "Details") view.download("Download as {}".format(extensions_mapping[extension]))
[docs] def delete(self, cancel=False): view = navigate_to(self, "Details") view.configuration.item_select( "Delete this Saved Report from the Database", handle_alert=not cancel ) if cancel: assert view.is_displayed view.flash.assert_no_error() else: view.flash.assert_no_error()
# TODO Doesn't work due to this BZ https://bugzilla.redhat.com/show_bug.cgi?id=1489387 # view.flash.assert_message("Successfully deleted Saved Report from the CFME Database") @property def exists(self): try: return bool(navigate_to(self, "Details")) except CandidateNotFound: return False
[docs]@attr.s class SavedReportsCollection(BaseCollection): ENTITY = SavedReport
[docs] def all(self): view = navigate_to(self.parent, "Details") results = [] try: for _ in view.saved_reports.paginator.pages(): for row in view.saved_reports.table.rows(): if not all([c[1].is_displayed for c in row]): # This is a temporary workaround for cases we have row span # greater that 1 column (e.g. in case of "Totals: ddd" column). # TODO: Support this functionality in widgetastic. Issue: # https://github.com/RedHatQE/widgetastic.core/issues/26 continue results.append( self.instantiate( row.run_at.text.encode("utf-8"), row.queued_at.text.encode("utf-8"), self.parent.is_candu ) ) except NoSuchElementException: pass return results
[docs]class SavedReportData(Pretty): """This class stores data retrieved from saved report. Args: headers: Tuple with header columns. body: List of tuples with body rows. """ pretty_attrs = ["headers", "body"] def __init__(self, headers, body): self.headers = headers self.body = body @property def rows(self): for row in self.body: yield dict(zip(self.headers, row))
[docs] def find_row(self, column, value): if column not in self.headers: return None for row in self.rows: if row[column] == value: return row
[docs] def find_cell(self, column, value, cell): try: return self.find_row(column, value)[cell] except TypeError: return None
[docs]@navigator.register(ReportsCollection, "All") class ReportsAll(CFMENavigateStep): VIEW = AllReportsView prerequisite = NavigateToAttribute("appliance.server", "CloudIntelReports")
[docs] def step(self): self.prerequisite_view.reports.tree.click_path("All Reports")
[docs]@navigator.register(ReportsCollection, "Add") class ReportsNew(CFMENavigateStep): VIEW = ReportAddView prerequisite = NavigateToAttribute("appliance.server", "CloudIntelReports")
[docs] def step(self): self.prerequisite_view.reports.tree.click_path("All Reports") self.prerequisite_view.configuration.item_select("Add a new Report")
[docs]@navigator.register(Report, "Edit") class ReportEdit(CFMENavigateStep): VIEW = ReportEditView prerequisite = NavigateToSibling("Details")
[docs] def step(self): self.prerequisite_view.configuration.item_select("Edit this Report")
[docs]@navigator.register(Report, "Details") class ReportDetails(CFMENavigateStep): VIEW = ReportDetailsView prerequisite = NavigateToAttribute("appliance.server", "CloudIntelReports")
[docs] def step(self): self.prerequisite_view.reports.tree.click_path( "All Reports", self.obj.type or self.obj.company_name, self.obj.subtype or "Custom", self.obj.menu_name ) self.view.report_info.select()
[docs]@navigator.register(SavedReport, "Details") class SavedReportDetails(CFMENavigateStep): VIEW = SavedReportDetailsView prerequisite = NavigateToAttribute("appliance.server", "CloudIntelReports")
[docs] def step(self): self.prerequisite_view.reports.tree.click_path( "All Reports", self.obj.report.type or self.obj.report.company_name, self.obj.report.subtype or "Custom", self.obj.report.menu_name, self.obj.datetime_in_tree )