Skip to content

Commit

Permalink
CV UI - Promote, Publish, Add Content (#939)
Browse files Browse the repository at this point in the history
* Edit/Versions/Publish/Promote work

* Large changes to make publish/promote/composite/add content work

* Small adjustments to docstrings

* Add wait_displayed to repo table read

* Testing a hard sleep

* Try different ways to wait on the table to load properly

* Add wait_displayed to read_cv method

Co-authored-by: David Moore <[email protected]>

* Add wait_for statements in various places to increase consistency

* Remove comment about sleep

---------

Co-authored-by: David Moore <[email protected]>
  • Loading branch information
sambible and damoore044 authored Oct 17, 2024
1 parent 36d08c8 commit 98c53e4
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 153 deletions.
140 changes: 118 additions & 22 deletions airgun/entities/contentview_new.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
from asyncio import wait_for
import time

from navmazing import NavigateToSibling
from widgetastic.exceptions import NoSuchElementException

from airgun.entities.base import BaseEntity
from airgun.navigation import NavigateStep, navigator
from airgun.utils import retry_navigation
from airgun.views.contentview_new import (
AddContentViewModal,
AddRPMRuleView,
ContentViewCreateView,
ContentViewEditView,
ContentViewTableView,
ContentViewVersionDetailsView,
ContentViewVersionPromoteView,
ContentViewVersionPublishView,
CreateFilterView,
EditFilterView,
Expand All @@ -19,11 +24,13 @@
class NewContentViewEntity(BaseEntity):
endpoint_path = '/content_views'

def create(self, values):
def create(self, values, composite=False):
"""Create a new content view"""
view = self.navigate_to(self, 'New')
self.browser.plugin.ensure_page_safe(timeout='5s')
view.wait_displayed()
if composite:
view.composite_tile.click()
view.fill(values)
view.submit.click()

Expand All @@ -34,27 +41,72 @@ def search(self, value):
view.wait_displayed()
return view.search(value)

def publish(self, entity_name, values=None):
"""Publishes new version of CV"""
def publish(self, entity_name, values=None, promote=False, lce=None):
"""Publishes new version of CV, optionally allowing for instant promotion"""
view = self.navigate_to(self, 'Publish', entity_name=entity_name)
self.browser.plugin.ensure_page_safe(timeout='5s')
view.wait_displayed()
if values:
view.fill(values)
view.next.click()
view.finish.click()
if promote:
view.promote.click()
view.lce_selector.fill({lce: True})
view.next_button.click()
view.finish_button.click()
wait_for(lambda: view.progressbar.is_displayed, timeout=10)
view.progressbar.wait_for_result()
view = self.navigate_to(self, 'Edit', entity_name=entity_name)
self.browser.plugin.ensure_page_safe(timeout='5s')
view.wait_displayed()
return view.versions.table.read()

def add_content(self, entity_name, content_name):
"""Add specified content to the given Content View"""
view = self.navigate_to(self, 'Edit', entity_name=entity_name)
self.browser.plugin.ensure_page_safe(timeout='5s')
view.wait_displayed()
wait_for(lambda: view.repositories.resources.is_displayed, timeout=10)
view.repositories.resources.add(content_name)
return view.repositories.resources.read()

def add_cv(self, ccv_name, cv_name, always_update=False, version=None):
"""Adds selected CV to selected CCV, optionally with support for always_update and specified version"""
view = self.navigate_to(self, 'Edit', entity_name=ccv_name)
self.browser.plugin.ensure_page_safe(timeout='5s')
view.wait_displayed()
view.content_views.resources.add(cv_name)
view = AddContentViewModal(self.browser)
if always_update:
view.always_update.fill(True)
if version:
view.version_select.item_select(version)
view.submit_button.click()
view = self.navigate_to(self, 'Edit', entity_name=ccv_name)
self.browser.plugin.ensure_page_safe(timeout='5s')
view.wait_displayed()
wait_for(lambda: view.content_views.resources.is_displayed, timeout=10)
return view.content_views.resources.read()

def read_cv(self, entity_name, version_name):
"""Reads the table for a specified Content View's specified Version"""
view = self.navigate_to(self, 'Edit', entity_name=entity_name)
self.browser.plugin.ensure_page_safe(timeout='5s')
view.wait_displayed()
view.versions.search(version_name)
return view.versions.table.row(version=version_name).read()

def read_repositories(self, entity_name):
"""Reads the repositories table for a specified Content View"""
view = self.navigate_to(self, 'Edit', entity_name=entity_name)
return view.repositories.resources.read()

def read_version_table(self, entity_name, version, tab_name, search_param=None):
"""Reads a specific table for a CV Version"""
view = self.navigate_to(self, 'Version', entity_name=entity_name, version=version)
self.browser.plugin.ensure_page_safe(timeout='5s')
view.wait_displayed()
# This allows dynamic access to the proper table
getattr(view, tab_name).table.wait_displayed()
wait_for(lambda: getattr(view, tab_name).table.wait_displayed(), timeout=10)
if search_param:
getattr(view, tab_name).searchbox.search(search_param)
return getattr(view, tab_name).table.read()
Expand Down Expand Up @@ -129,6 +181,29 @@ def read_french_lang_cv(self):
view.wait_displayed()
return view.table.read()

def promote(self, entity_name, version_name, lce_name):
"""Promotes the selected version of content view to given environment.
:return: dict with new content view version table row; contains keys
like 'Version', 'Status', 'Environments' etc.
"""
view = self.navigate_to(self, 'Promote', entity_name=entity_name, version_name=version_name)
modal = ContentViewVersionPromoteView(self.browser)
if modal.is_displayed:
modal.lce.fill({lce_name: True})
modal.promote_btn.click()
view = self.navigate_to(self, 'Edit', entity_name=entity_name)
view.versions.search(version_name)
return view.versions.table.row(version=version_name).read()

def update(self, entity_name, values):
"""Update existing content view"""
view = self.navigate_to(self, 'Edit', entity_name=entity_name)
time.sleep(3)
filled_values = view.fill(values)
view.flash.assert_no_error()
view.flash.dismiss()
return filled_values


@navigator.register(NewContentViewEntity, 'All')
class ShowAllContentViewsScreen(NavigateStep):
Expand Down Expand Up @@ -164,22 +239,6 @@ def step(self, *args, **kwargs):
self.parent.create_content_view.click()


@navigator.register(NewContentViewEntity, 'Publish')
class PublishContentViewVersion(NavigateStep):
"""Navigate to Content View Publish screen."""

VIEW = ContentViewVersionPublishView

def prerequisite(self, *args, **kwargs):
"""Open Content View first."""
return self.navigate_to(self.obj, 'Edit', entity_name=kwargs.get('entity_name'))

@retry_navigation
def step(self, *args, **kwargs):
"""Click 'Publish new version' button"""
self.parent.publish.click()


@navigator.register(NewContentViewEntity, 'Edit')
class EditContentView(NavigateStep):
"""Navigate to Edit Content View screen."""
Expand Down Expand Up @@ -208,3 +267,40 @@ def step(self, *args, **kwargs):
version = kwargs.get('version')
self.parent.versions.search(version)
self.parent.versions.table.row(version=version)['Version'].widget.click()


@navigator.register(NewContentViewEntity, 'Publish')
class PublishContentViewVersion(NavigateStep):
"""Navigate to Content View Publish screen.
Args:
entity_name: name of content view
"""

VIEW = ContentViewVersionPublishView

def prerequisite(self, *args, **kwargs):
"""Open Content View first."""
return self.navigate_to(self.obj, 'Edit', entity_name=kwargs.get('entity_name'))

def step(self, *args, **kwargs):
"""Click 'Publish new version' button"""
self.parent.publish.click()


@navigator.register(NewContentViewEntity, 'Promote')
class PromoteContentViewVersion(NavigateStep):
"""Navigate to Content View Promote screen.
Args:
entity_name: name of content view
version_name: name of content view version to promote
"""

VIEW = ContentViewEditView

def prerequisite(self, *args, **kwargs):
return self.navigate_to(self.obj, 'Edit', entity_name=kwargs.get('entity_name'))

def step(self, *args, **kwargs):
version_name = kwargs.get('version_name')
self.parent.versions.search(version_name)
self.parent.versions.table[0][7].widget.item_select('Promote')
121 changes: 69 additions & 52 deletions airgun/views/common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import time

import wait_for
from widgetastic.widget import (
Checkbox,
ConditionalSwitchableView,
Expand All @@ -12,9 +9,10 @@
WTMixin,
do_not_read_this_widget,
)
from widgetastic_patternfly import BreadCrumb, Button, Tab, TabWithDropdown
from widgetastic_patternfly import BreadCrumb, Tab, TabWithDropdown
from widgetastic_patternfly4 import Button, Select
from widgetastic_patternfly4.navigation import Navigation
from widgetastic_patternfly4.ouia import Button as PF4Button, Dropdown, PatternflyTable
from widgetastic_patternfly4.ouia import Dropdown, PatternflyTable

from airgun.utils import get_widget_by_name, normalize_dict_values
from airgun.widgets import (
Expand All @@ -25,6 +23,7 @@
ItemsList,
LCESelector,
Pf4ConfirmationDialog,
PF4LCECheckSelector,
PF4LCESelector,
PF4NavSearch,
PF4Search,
Expand Down Expand Up @@ -285,6 +284,30 @@ class PF4LCESelectorGroup(LCESelectorGroup):
)


class PF4LCECheckSelectorGroup(PF4LCESelectorGroup):
"""Checkbox version of PF4 LCE Selector"""

lce = PF4LCECheckSelector(
locator=ParametrizedLocator(
'.//div[@class="env-path" and .//*[contains(normalize-space(.), "{lce_name}")]]'
)
)


class PF4LCEGroup(ParametrizedLocator):
"Group of LCE indicators"
ROOT = './/td and '

PARAMETERS = ('lce_name',)

LAST_ENV = './/div[@class="env-path"][last()]'
lce = PF4LCESelector(
locator=ParametrizedLocator(
'.//div[@class="env-path" and .//*[contains(normalize-space(.), "{lce_name}")]]'
)
)


class ListRemoveTab(SatSecondaryTab):
"""'List/Remove' tab, part of :class:`AddRemoveResourcesView`."""

Expand Down Expand Up @@ -390,38 +413,13 @@ def read(self):
}


class AddRemoveSubscriptionsView(AddRemoveResourcesView):
"""A variant of :class:`AddRemoveResourcesView` for managing subscriptions.
Subscriptions table has different structure - entity label is located in
separate row apart from checkbox and other cells.
"""

@View.nested
class list_remove_tab(ListRemoveTab):
table = SatSubscriptionsTable(
locator=".//table", column_widgets={0: Checkbox(locator=".//input[@type='checkbox']")}
)

@View.nested
class add_tab(AddTab):
table = SatSubscriptionsTable(
locator=".//table", column_widgets={0: Checkbox(locator=".//input[@type='checkbox']")}
)


class NewAddRemoveResourcesView(View):
searchbox = PF4Search()
type = Dropdown(
locator='.//div[contains(@class, "All repositories") or'
' contains(@aria-haspopup="listbox")]'
)
Status = Dropdown(
locator='.//div[contains(@class, "All") or contains(@aria-haspopup="listbox")]'
)
add_repo = PF4Button('OUIA-Generated-Button-secondary-2')
# Need to add kebab menu
status = Select(locator='.//div[@data-ouia-component-id="select Status"]')
remove_button = Dropdown(locator='.//div[@data-ouia-component-id="repositoies-bulk-actions"]')
add_button = Button(locator='.//button[@data-ouia-component-id="add-repositories"]')
table = PatternflyTable(
component_id='OUIA-Generated-Table-4',
component_id='content-view-repositories-table',
column_widgets={
0: Checkbox(locator='.//input[@type="checkbox"]'),
'Type': Text('.//a'),
Expand All @@ -433,49 +431,68 @@ class NewAddRemoveResourcesView(View):
},
)

def select_status(self, value):
"""Set status box to passed in value"""
self.status.fill(value)

def search(self, value):
"""Search for specific available resource and return the results"""
self.searchbox.search(value)
# Tried following ways to wait for table to be displayed, only sleep worked
# Might need a before/after fill
wait_for(
lambda: self.table.is_displayed is True,
timeout=60,
delay=1,
)
time.sleep(3)
self.table.wait_displayed()
return self.table.read()
return self.read()

def add(self, value):
"""Associate specific resource"""
self.select_status("Not added")
self.search(value)
value = self.table.rows()
next(self.table.rows())[0].widget.fill(True)
self.add_repo.click()
self.add_button.click()

def fill(self, values):
"""Associate resource(s)"""
if not isinstance(values, list):
values = [
values,
]
for value in values:
self.add(value)

def remove(self, value):
"""Unassign some resource(s).
:param str or list values: string containing resource name or a list of
such strings.
:param str or list values: string containing resource name or a list of such strings.
"""
self.select_status("Added")
self.search(value)
next(self.table.rows())[0].widget.fill(True)
self.remove_button.click()
self.remove_button.item_select('Remove')

def read(self):
"""Read all table values from both resource tables"""
self.browser.wait_for_element(locator='//h4[text()="Loading"]', exception=False)
self.browser.wait_for_element(
self.table, exception=False, ensure_page_safe=True, timeout=10
)
self.browser.plugin.ensure_page_safe(timeout='60s')
self.table.wait_displayed()
self.select_status("All")
return self.table.read()


class AddRemoveSubscriptionsView(AddRemoveResourcesView):
"""A variant of :class:`AddRemoveResourcesView` for managing subscriptions.
Subscriptions table has different structure - entity label is located in
separate row apart from checkbox and other cells.
"""

@View.nested
class list_remove_tab(ListRemoveTab):
table = SatSubscriptionsTable(
locator=".//table", column_widgets={0: Checkbox(locator=".//input[@type='checkbox']")}
)

@View.nested
class add_tab(AddTab):
table = SatSubscriptionsTable(
locator=".//table", column_widgets={0: Checkbox(locator=".//input[@type='checkbox']")}
)


class TemplateEditor(View):
"""Default view for template entity editor that can be present for example
on provisioning template of partition table pages. It contains from
Expand Down
Loading

0 comments on commit 98c53e4

Please sign in to comment.