diff --git a/airgun/entities/job_invocation.py b/airgun/entities/job_invocation.py index 19f90665c..5fd1575ec 100644 --- a/airgun/entities/job_invocation.py +++ b/airgun/entities/job_invocation.py @@ -11,6 +11,7 @@ JobInvocationCreateView, JobInvocationStatusView, JobInvocationsView, + NewJobInvocationStatusView, ) @@ -30,9 +31,10 @@ def search(self, value): view = self.navigate_to(self, 'All') return view.search(value) - def read(self, entity_name, host_name, widget_names=None): + def read(self, entity_name, host_name, widget_names=None, new_ui=False): """Read values for scheduled or already executed job""" - view = self.navigate_to(self, 'Job Status', entity_name=entity_name, host_name=host_name) + nav_step = 'Job Status' if not new_ui else 'Job Status New UI' + view = self.navigate_to(self, nav_step, entity_name=entity_name, host_name=host_name) return view.read(widget_names=widget_names) def wait_job_invocation_state(self, entity_name, host_name, expected_state='succeeded'): @@ -124,3 +126,29 @@ def prerequisite(self, *args, **kwargs): def step(self, *args, **kwargs): self.parent.search(f'host = {kwargs.get("host_name")}') self.parent.table.row(description=kwargs.get('entity_name'))['Description'].widget.click() + + +@navigator.register(JobInvocationEntity, 'Job Status New UI') +class NewJobStatus(NavigateStep): + """Navigate to the new job invocation details page. + Note: `Show Experimental Labs` setting must be enabled. + + Args: + entity_name: name of the job + host_name: name of the host to which job was applied + """ + + VIEW = NewJobInvocationStatusView + + def prerequisite(self, *args, **kwargs): + return self.navigate_to( + self.obj, + 'Job Status', + entity_name=kwargs.get('entity_name'), + host_name=kwargs.get('host_name'), + ) + + def step(self, *args, **kwargs): + self.parent.new_ui.click() + self.view.browser.plugin.ensure_page_safe() + self.view.wait_displayed() diff --git a/airgun/views/job_invocation.py b/airgun/views/job_invocation.py index 0d9041d10..07290f69d 100644 --- a/airgun/views/job_invocation.py +++ b/airgun/views/job_invocation.py @@ -1,8 +1,13 @@ from wait_for import wait_for from widgetastic.widget import Text, TextInput, View from widgetastic_patternfly import BreadCrumb -from widgetastic_patternfly4 import Button, ChipGroup, Radio, Select -from widgetastic_patternfly4.ouia import Select as OUIASelect +from widgetastic_patternfly4 import Button, ChipGroup, DescriptionList, Radio, Select +from widgetastic_patternfly4.donutchart import DonutCircle, DonutLegend +from widgetastic_patternfly4.ouia import ( + Dropdown as OUIADropdown, + Select as OUIASelect, + Text as OUIAText, +) from airgun.views.common import ( BaseLoggedInView, @@ -152,6 +157,7 @@ def is_displayed(self): job_task = Text("//a[normalize-space(.)='Job Task']") cancel_job = Button(value='Cancel Job') abort_job = Button(value='Abort Job') + new_ui = Text("//a[normalize-space(.)='New UI']") @View.nested class overview(SatTab): @@ -196,3 +202,66 @@ def wait_for_result(self, timeout=600, delay=1): delay=1, logger=self.logger, ) + + +class NewJobInvocationStatusView(BaseLoggedInView): + breadcrumb = BreadCrumb() + title = OUIAText('breadcrumb_title') + create_report = Button(value='Create report') + actions = OUIADropdown('job-invocation-global-actions-dropdown') + BREADCRUMB_LENGTH = 2 + + @property + def is_displayed(self): + breadcrumb_loaded = self.breadcrumb.wait_displayed() + title_loaded = self.title.wait_displayed() + data_loaded, _ = wait_for( + func=lambda: self.status.is_displayed, + timeout=60, + delay=15, + fail_func=self.browser.refresh, + ) + return ( + breadcrumb_loaded + and title_loaded + and data_loaded + and self.breadcrumb.locations[0] == 'Jobs' + and len(self.breadcrumb.locations) == self.BREADCRUMB_LENGTH + ) + + @View.nested + class overall_status(DonutCircle): + """The donut circle with the overall job status of '{succeeded hosts}/{total hosts}'""" + + def read(self): + """Return `dict` with the parsed overall status numbers, for example: + ```{'succeeded_hosts': 2, 'total_hosts': 5}``` + """ + succeeded_hosts, total_hosts = self.labels[0].split('/') + return {'succeeded_hosts': int(succeeded_hosts), 'total_hosts': int(total_hosts)} + + @View.nested + class status(DonutLegend): + """'System status' panel.""" + + ROOT = ".//div[contains(@class, 'chart-legend')]" + first_label = Text(locator="//*[@id='legend-labels-0']") + + @property + def is_displayed(self): + """Any status label is displayed after all data are loaded.""" + return self.first_label.is_displayed + + def read(self): + """Return `dict` with the System status info. + Example: ```{'Succeeded': 2, 'Failed': 1, 'In Progress': 0, 'Canceled': 0}``` + """ + return {item['label']: int(item['value']) for item in self.all_items} + + @View.nested + class overview(DescriptionList): + ROOT = ".//div[contains(@class, 'job-overview')]" + + def read(self): + """Return `dict` without trailing ':' in the key names.""" + return {key.replace(':', ''): val for key, val in super().read().items()}