Skip to content

Commit

Permalink
Merge pull request #15800 from jmchilton/object_store_selection_tests
Browse files Browse the repository at this point in the history
Initial end-to-end tests for separate quota sources per object store
  • Loading branch information
mvdbeek authored Mar 31, 2023
2 parents bda2ddf + 6c2a011 commit 13e2e50
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
:title="reloadButtonTitle"
:variant="reloadButtonVariant"
size="sm"
class="rounded-0 text-decoration-none"
class="rounded-0 text-decoration-none history-refresh-button"
@click="reloadContents()">
<span :class="reloadButtonCls" />
</b-button>
Expand Down
8 changes: 6 additions & 2 deletions client/src/components/User/DiskUsage/Quota/QuotaUsageBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,15 @@ defineExpose({
</span>
{{ storageSourceText }}
</component>
<component :is="usageTag" v-if="!compact">
<component
:is="usageTag"
v-if="!compact"
:data-quota-usage="quotaUsage.totalDiskUsageInBytes"
class="quota-usage">
<b>{{ quotaUsage.niceTotalDiskUsage }}</b>
<span v-if="quotaHasLimit"> of {{ quotaUsage.niceQuota }}</span> used
</component>
<span v-if="quotaHasLimit && !compact" class="quota-percent-text">
<span v-if="quotaHasLimit && !compact" class="quota-percent-text" :data-quota-percent="quotaUsage.quotaPercent">
{{ quotaUsage.quotaPercent }}{{ percentOfDiskQuotaUsedText }}
</span>
<b-progress
Expand Down
14 changes: 12 additions & 2 deletions client/src/utils/navigation/navigation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,10 @@ object_store_details:
selectors:
stored_by_name: '.display-os-by-name'
badge_of_type: '.object-store-badge-wrapper [data-badge-type="${type}"]'

usage_details: '.quota-usage-bar'
usage_bytes: '.quota-usage'
usage_percent: '.quota-percent-text'
usage_progress: '.quota-usage-bar .progress'

history_panel:
menu:
Expand Down Expand Up @@ -207,7 +210,7 @@ history_panel:
alltags: '${_} .stateless-tags .tag'
metadata_file_download: '${_} [data-description="download ${metadata_name}"]'

dataset_operations_dropdown: '${_} .dataset-actions'
dataset_operations: '${_} .dataset-actions'

# re-usable history editor, scoped for use in different layout scenarios (multi, etc.)
editor:
Expand Down Expand Up @@ -782,6 +785,13 @@ admin:
search_results: '#shed-search-results'
upgrade_notification: '#repository-table .badge'

quota:
selectors:
add_new: '.quotas .manage-table-actions .action-button'
items: '.quotas tbody tr td'
add_form: "[url='/admin/create_quota']"
add_form_submit: "[url='/admin/create_quota'] #submit"

index:
selectors:
datatypes: '#admin-link-datatypes'
Expand Down
2 changes: 1 addition & 1 deletion lib/galaxy/navigation/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def description(self) -> str:
@property
@abc.abstractmethod
def component_locator(self) -> LocatorT:
"""Return a (by, selector) Selenium elment locator tuple for this selector."""
"""Return a (by, selector) Selenium element locator tuple for this selector."""

@property
def selenium_locator(self) -> Tuple[str, str]:
Expand Down
21 changes: 17 additions & 4 deletions lib/galaxy/selenium/has_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
Union,
)

from selenium.common.exceptions import TimeoutException as SeleniumTimeoutException
from selenium.common.exceptions import (
NoSuchElementException,
TimeoutException as SeleniumTimeoutException,
)
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
Expand Down Expand Up @@ -76,8 +79,15 @@ def assert_selector_absent(self, selector: str):
def find_elements(self, selector_template: Target) -> List[WebElement]:
return self.driver.find_elements(*selector_template.element_locator)

def assert_absent(self, selector_template: Target):
assert len(self.find_elements(selector_template)) == 0
def assert_absent(self, selector_template: Target) -> None:
elements = self.find_elements(selector_template)
if len(elements) != 0:
description = selector_template.description
any_displayed = False
for element in elements:
any_displayed = any_displayed or element.is_displayed()
msg = f"Expected DOM elements [{elements}] to be empty for selector target {description} - any actually displayed? [{any_displayed}]"
raise AssertionError(msg)

def element_absent(self, selector_template: Target) -> bool:
return len(self.find_elements(selector_template)) == 0
Expand Down Expand Up @@ -239,7 +249,10 @@ def click_selector(self, selector: str):

def fill(self, form: WebElement, info: dict):
for key, value in info.items():
input_element = form.find_element(By.NAME, key)
try:
input_element = form.find_element(By.NAME, key)
except NoSuchElementException:
input_element = form.find_element(By.ID, key)
input_element.send_keys(value)

def click_submit(self, form: WebElement):
Expand Down
58 changes: 57 additions & 1 deletion lib/galaxy/selenium/navigates_galaxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,11 @@ def history_panel_wait_for_hid_ok(self, hid, allowed_force_refreshes=0):
def history_panel_wait_for_hid_deferred(self, hid, allowed_force_refreshes=0):
return self.history_panel_wait_for_hid_state(hid, "deferred", allowed_force_refreshes=allowed_force_refreshes)

def wait_for_hid_ok_and_open_details(self, hid):
self.history_panel_wait_for_hid_ok(hid, allowed_force_refreshes=1)
self.history_panel_click_item_title(hid=hid)
self.history_panel_item_view_dataset_details(hid)

def history_panel_item_component(self, history_item=None, hid=None, multi_history_panel=False):
assert hid
return self.content_item_by_attributes(hid=hid, multi_history_panel=multi_history_panel)
Expand Down Expand Up @@ -1078,6 +1083,41 @@ def navigate_to_pages(self):
def admin_open(self):
self.components.masthead.admin.wait_for_and_click()

def create_quota(
self,
name: Optional[str] = None,
description: Optional[str] = None,
amount: Optional[str] = None,
quota_source_label: Optional[str] = None,
user: Optional[str] = None,
):
admin_component = self.components.admin

self.admin_open()
quota_link = admin_component.index.quotas
quota_link.wait_for_and_click()
quota_component = admin_component.quota

quota_component.add_new.wait_for_and_click()
form = quota_component.add_form.wait_for_visible()

name = name or self._get_random_name()
description = description or f"quota description for {name}"
amount = amount or ""
self.fill(
form,
{
"name": name,
"description": description,
"amount": amount,
},
)
if quota_source_label:
self.select2_set_value("#quota_source_label", quota_source_label)
if user:
self.select2_set_value("#in_users", user)
quota_component.add_form_submit.wait_for_and_click()

def select_dataset_from_lib_import_modal(self, filenames):
for name in filenames:
self.components.libraries.folder.select_import_dir_item(name=name).wait_for_and_click()
Expand Down Expand Up @@ -1376,6 +1416,22 @@ def datasource_tool_open(self, tool_id):
self.driver.execute_script("arguments[0].scrollIntoView(true);", tool_element)
tool_link.wait_for_and_click()

def run_environment_test_tool(self, inttest_value="42", select_storage: Optional[str] = None):
self.home()
self.tool_open("environment_variables")
if select_storage:
self.components.tool_form.storage_options.wait_for_and_click()
self.select_storage(select_storage)
self.tool_set_value("inttest", inttest_value)
self.tool_form_execute()

def select_storage(self, storage_id: str) -> None:
selection_component = self.components.preferences.object_store_selection
selection_component.option_buttons.wait_for_present()
button = selection_component.option_button(object_store_id=storage_id)
button.wait_for_and_click()
selection_component.option_buttons.wait_for_absent_or_hidden()

def create_page_and_edit(self, name=None, slug=None, screenshot_name=None):
name = self.create_page(name=name, slug=slug, screenshot_name=screenshot_name)
self.click_grid_popup_option(name, "Edit content")
Expand Down Expand Up @@ -1595,7 +1651,7 @@ def history_multi_view_display_collection_contents(self, collection_hid, collect

def history_panel_item_view_dataset_details(self, hid):
item = self.history_panel_item_component(hid=hid)
item.dataset_operations_dropdown.wait_for_and_click()
item.dataset_operations.wait_for_visible()
item.info_button.wait_for_and_click()
self.components.dataset_details._.wait_for_visible()

Expand Down
6 changes: 2 additions & 4 deletions lib/galaxy_test/selenium/test_history_dataset_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,13 @@ def _assert_action_buttons(self, hid, expected_buttons=None):

def _assert_downloadable(self, hid, is_downloadable=True):
item = self.history_panel_item_component(hid=hid)
item.dataset_operations_dropdown.wait_for_and_click()
item.dataset_operations.wait_for_visible()
item.info_button.wait_for_visible()
if is_downloadable:
assert item.download_button.is_displayed
else:
item.download_button.assert_absent_or_hidden()

# close menu...
item.dataset_operations_dropdown.wait_for_and_click()
item.dataset_operations.wait_for_visible()
self.sleep_for(self.wait_types.UX_RENDER)

def _assert_buttons(self, hid, expected_buttons):
Expand Down
111 changes: 73 additions & 38 deletions test/integration_selenium/test_objectstore_selection.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import string
from typing import (
Optional,
TYPE_CHECKING,
)
from typing import TYPE_CHECKING

from galaxy_test.driver.integration_util import ConfiguresObjectStores
from galaxy_test.selenium.framework import managed_history
Expand Down Expand Up @@ -96,10 +93,8 @@ def handle_galaxy_config_kwds(cls, config):
@selenium_test
@managed_history
def test_0_tools_to_default(self):
self._run_environment_test_tool()
self.history_panel_wait_for_hid_ok(1)
self.history_panel_click_item_title(hid=1)
self.history_panel_item_view_dataset_details(1)
self.run_environment_test_tool()
self.wait_for_hid_ok_and_open_details(1)
details = self.components.object_store_details
text = details.stored_by_name.wait_for_text()
assert "High Performance Storage" in text
Expand All @@ -109,11 +104,8 @@ def test_0_tools_to_default(self):
@selenium_test
@managed_history
def test_1_tools_override_run(self):
self._run_environment_test_tool(select_storage="second")

self.history_panel_wait_for_hid_ok(1)
self.history_panel_click_item_title(hid=1)
self.history_panel_item_view_dataset_details(1)
self.run_environment_test_tool(select_storage="second")
self.wait_for_hid_ok_and_open_details(1)
details = self.components.object_store_details
text = details.stored_by_name.wait_for_text()
assert "Second Tier Storage" in text
Expand All @@ -125,11 +117,9 @@ def test_2_user_override(self):
self.navigate_to_user_preferences()
preferences = self.components.preferences
preferences.preferred_storage.wait_for_and_click()
self._select_storage("second")
self._run_environment_test_tool()
self.history_panel_wait_for_hid_ok(1)
self.history_panel_click_item_title(hid=1)
self.history_panel_item_view_dataset_details(1)
self.select_storage("second")
self.run_environment_test_tool()
self.wait_for_hid_ok_and_open_details(1)
details = self.components.object_store_details
text = details.stored_by_name.wait_for_text()
assert "Second Tier Storage" in text
Expand All @@ -145,30 +135,75 @@ def test_3_user_un_override(self):
self.navigate_to_user_preferences()
preferences = self.components.preferences
preferences.preferred_storage.wait_for_and_click()
self._select_storage("__null__")
self.select_storage("__null__")

self._run_environment_test_tool()
self.history_panel_wait_for_hid_ok(1)
self.history_panel_click_item_title(hid=1)
self.history_panel_item_view_dataset_details(1)
self.run_environment_test_tool()
self.wait_for_hid_ok_and_open_details(1)
details = self.components.object_store_details
text = details.stored_by_name.wait_for_text()
assert "High Performance Storage" in text
details.badge_of_type(type="faster").wait_for_present()
details.badge_of_type(type="more_stable").wait_for_present()

def _run_environment_test_tool(self, inttest_value="42", select_storage: Optional[str] = None):
self.home()
self.tool_open("environment_variables")
if select_storage:
self.components.tool_form.storage_options.wait_for_and_click()
self._select_storage(select_storage)
self.tool_set_value("inttest", inttest_value)
self.tool_form_execute()

def _select_storage(self, storage_id: str) -> None:
selection_component = self.components.preferences.object_store_selection
selection_component.option_buttons.wait_for_present()
button = selection_component.option_button(object_store_id=storage_id)
button.wait_for_and_click()
selection_component.option_buttons.wait_for_absent_or_hidden()

class TestMultipleQuotasSeleniumIntegration(SeleniumIntegrationTestCase, ConfiguresObjectStores):
dataset_populator: "SeleniumSessionDatasetPopulator"
run_as_admin = True

@classmethod
def handle_galaxy_config_kwds(cls, config):
cls._configure_object_store(MSI_EXAMPLE_OBJECT_STORE_CONFIG_TEMPLATE, config)
config["enable_quotas"] = True

@selenium_test
def test_multiple_quota_sources_for_user(self):
expected_bytes = 17

# create a user to create a quota for...
user_email = self._get_random_email("quota_user")
self.register(user_email)
self.logout()

# give them a quota on second_tier of 10 times the
# size of the output of the test tool.

self.admin_login()
quota_name = self._get_random_name(prefix="secondquota")
self.create_quota(
name=quota_name,
amount=f"{expected_bytes * 10} B",
quota_source_label="second_tier",
user=user_email,
)
admin_component = self.components.admin
quota_component = admin_component.quota
quota_component.items.wait_for_element_count_of_at_least(1)
self.logout()

# run the tool twice - once in the default object store without
# quota configured and once in second_tier storage with a quota
# configured.
self.submit_login(user_email)
self.run_environment_test_tool()
self.sleep_for(self.wait_types.UX_TRANSITION)
self.run_environment_test_tool(select_storage="second")

# Assert no quota information on an object store without
# quota configured and check the usage and percent on the
# the dataset stored in an object store with a configured
# quota.

details = self.components.object_store_details
self.wait_for_hid_ok_and_open_details(1)
details.usage_percent.assert_absent()

self.wait_for_hid_ok_and_open_details(2)

usage_summary_el = details.usage_details.wait_for_visible()
assert usage_summary_el.get_attribute("quota-source-label") == "second_tier"

bytes_el = details.usage_bytes.wait_for_visible()
assert bytes_el.get_attribute("data-quota-usage") == f"{expected_bytes}"

percent_el = details.usage_percent.wait_for_visible()
assert percent_el.get_attribute("data-quota-percent") == "10"

0 comments on commit 13e2e50

Please sign in to comment.