Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial end-to-end tests for separate quota sources per object store #15800

Merged
merged 2 commits into from
Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -781,6 +784,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"