Disable action buttons according to the statuses of environments
In environments list view, action buttons are only clickable if all checked environments have appropriate status to run this action. Page reload after environment status change is added to refresh displayed buttons. Selenium tests to check buttons availability are added. Change-Id: Iae50fb87697c3fa745e412f1cab50dc0da9d981b Closes-bug: #1593292
This commit is contained in:
parent
d791127dbd
commit
f719229431
|
@ -379,6 +379,11 @@ class ShowEnvironmentServices(tables.LinkAction):
|
|||
class UpdateEnvironmentRow(tables.Row):
|
||||
ajax = True
|
||||
|
||||
def __init__(self, table, datum=None):
|
||||
super(UpdateEnvironmentRow, self).__init__(table, datum)
|
||||
if hasattr(datum, 'status'):
|
||||
self.attrs['status'] = datum.status
|
||||
|
||||
def get_data(self, request, environment_id):
|
||||
try:
|
||||
return api.environment_get(request, environment_id)
|
||||
|
|
|
@ -50,7 +50,10 @@ $(function() {
|
|||
});
|
||||
|
||||
var reloadEnvironmentCalled = false;
|
||||
var lastStatuses = [];
|
||||
|
||||
// Reload page after table update if no more environments left
|
||||
// or status of some environment changed
|
||||
$(function() {
|
||||
"use strict";
|
||||
$("table#environments").on("update", function () {
|
||||
|
@ -60,6 +63,104 @@ $(function() {
|
|||
reloadEnvironmentCalled = true;
|
||||
location.reload(true);
|
||||
}
|
||||
} else {
|
||||
var $statuses = [];
|
||||
for (var $i = 0; $i < $environmentsRows.length; $i++) {
|
||||
var $row = $($environmentsRows[$i]);
|
||||
var $rowStatus = getRowStatus($row);
|
||||
$statuses.push($rowStatus);
|
||||
}
|
||||
if (lastStatuses.length !== 0 && areArraysEqual($statuses, lastStatuses) === false) {
|
||||
if (reloadEnvironmentCalled === false) {
|
||||
reloadEnvironmentCalled = true;
|
||||
location.reload(true);
|
||||
}
|
||||
} else {
|
||||
lastStatuses = $statuses;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function getRowStatus($row) {
|
||||
"use strict";
|
||||
if ($row.hasClass('status_unknown')) {
|
||||
return "in process";
|
||||
} else {
|
||||
return $row.attr("status");
|
||||
}
|
||||
}
|
||||
|
||||
function areArraysEqual($arr1, $arr2) {
|
||||
"use strict";
|
||||
if ($arr1.length !== $arr2.length) {
|
||||
return false;
|
||||
}
|
||||
for (var $i = 0; $i < $arr1.length; $i++) {
|
||||
if ($arr1[$i] !== $arr2[$i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Disable action buttons according to the statuses of checked environments
|
||||
$(function() {
|
||||
"use strict";
|
||||
var $statuses = {
|
||||
environments__deploy: ['pending', 'deploy failure'],
|
||||
environments__delete: ['ready', 'pending', 'new', 'deploy failure'],
|
||||
environments__abandon: ['ready', 'in process', 'deploy failure', 'delete failure']
|
||||
};
|
||||
|
||||
// Change of individual checkboxes or table update
|
||||
// TODO(vakovalchuk): improve checkbox detection on the deploying rows
|
||||
// Deploying rows don't react to selectors less broad than table body, e.g.:
|
||||
// $("table#environments tbody input[type='checkbox']").change(enableButtons);
|
||||
$("table#environments tbody").click(enableButtons);
|
||||
$("table#environments").on("update", enableButtons);
|
||||
|
||||
function enableButtons() {
|
||||
var $buttons = $("table#environments div.table_actions").find('button[name="action"]');
|
||||
var $environmentsRows = $("table#environments").find('tbody tr:visible').not('.empty');
|
||||
for (var $i = 0; $i < $buttons.length; $i++) {
|
||||
var $buttonValue = $buttons[$i].value;
|
||||
for (var $j = 0; $j < $environmentsRows.length; $j++) {
|
||||
var $row = $($environmentsRows[$j]);
|
||||
var $checkbox = $row.find("input.table-row-multi-select").first();
|
||||
if ($checkbox.prop('checked')) {
|
||||
var $rowStatus = getRowStatus($row);
|
||||
if ($statuses[$buttonValue].indexOf($rowStatus) === -1) {
|
||||
$($buttons[$i]).prop("disabled", true);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$($buttons[$i]).prop("disabled", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Change of all checkboxes at once
|
||||
$("table#environments thead input.table-row-multi-select:checkbox").change(function () {
|
||||
var $buttons = $("table#environments div.table_actions").find('button[name="action"]');
|
||||
var $environmentsRows = $("table#environments").find('tbody tr:visible').not('.empty');
|
||||
if ($(this).prop('checked')) {
|
||||
for (var $j = 0; $j < $buttons.length; $j++) {
|
||||
var $buttonValue = $buttons[$j].value;
|
||||
for (var $k = 0; $k < $environmentsRows.length; $k++) {
|
||||
var $row = $($environmentsRows[$k]);
|
||||
var $rowStatus = getRowStatus($row);
|
||||
if ($statuses[$buttonValue].indexOf($rowStatus) === -1) {
|
||||
$($buttons[$j]).prop("disabled", true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var $l = 0; $l < $buttons.length; $l++) {
|
||||
$($buttons[$l]).prop("disabled", false);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
Namespaces:
|
||||
=: io.murano.apps
|
||||
std: io.murano
|
||||
|
||||
Name: DeployingApp
|
||||
|
||||
Extends: std:Application
|
||||
|
||||
Properties:
|
||||
name:
|
||||
Contract: $.string().notNull()
|
||||
|
||||
Methods:
|
||||
testAction:
|
||||
Usage: Action
|
||||
Body:
|
||||
- sleep(3)
|
||||
- $this.find(std:Environment).reporter.report($this, 'Completed')
|
||||
deploy:
|
||||
Body:
|
||||
- sleep(30)
|
||||
- $this.find(std:Environment).reporter.report($this, 'Follow the white rabbit')
|
|
@ -0,0 +1,114 @@
|
|||
#Note: it is a fake application, it isn't intended to be deployed
|
||||
Version: 2
|
||||
|
||||
|
||||
Application:
|
||||
?:
|
||||
type: io.murano.apps.DeployingApp
|
||||
name: $.appConfiguration.name
|
||||
|
||||
|
||||
Forms:
|
||||
- appConfiguration:
|
||||
fields:
|
||||
- name: title
|
||||
type: string
|
||||
required: false
|
||||
hidden: true
|
||||
description: >-
|
||||
Fields with different types are presented in this step
|
||||
- name: domain
|
||||
type: string
|
||||
required: false
|
||||
label: String - Domain Name
|
||||
description: >-
|
||||
Requirements: only A-Z, a-z, 0-9, (.) and (-) and should not end with a dash.
|
||||
Note: Only first 15 characters or characters
|
||||
before first period is used as NetBIOS name, min/max length are defined
|
||||
minLength: 2
|
||||
maxLength: 255
|
||||
validators:
|
||||
- expr:
|
||||
regexpValidator: '^([0-9A-Za-z]|[0-9A-Za-z][0-9A-Za-z-]*[0-9A-Za-z])\.[0-9A-Za-z][0-9A-Za-z-]*[0-9A-Za-z]$'
|
||||
message: >-
|
||||
Only letters, numbers and dashes in the middle are
|
||||
allowed. Period characters are allowed only when they
|
||||
are used to delimit the components of domain style
|
||||
names. Single-level domain is not
|
||||
appropriate. Subdomains are not allowed.
|
||||
|
||||
- expr:
|
||||
regexpValidator: '(^[^.]+$|^[^.]{1,15}\..*$)'
|
||||
message: >-
|
||||
NetBIOS name cannot be shorter than 1 symbol and
|
||||
longer than 15 symbols.
|
||||
|
||||
- expr:
|
||||
regexpValidator: '(^[^.]+$|^[^.]*\.[^.]{2,63}.*$)'
|
||||
message: >-
|
||||
DNS host name cannot be shorter than 2 symbols and
|
||||
longer than 63 symbols.
|
||||
helpText: >-
|
||||
Just letters, numbers and dashes are allowed.
|
||||
A dot can be used to create subdomains
|
||||
|
||||
- name: name
|
||||
type: string
|
||||
label: Application Name
|
||||
description: >-
|
||||
Requirements: Just A-Z, a-z, 0-9, dash and
|
||||
underline are allowed, min/max value are defined.
|
||||
minLength: 2
|
||||
maxLength: 12
|
||||
regexpValidator: '^[-\w]+$'
|
||||
errorMessages:
|
||||
invalid: Just letters, numbers, underscores and hyphens are allowed.
|
||||
helpText: Just letters, numbers, underscores and hyphens are allowed.
|
||||
|
||||
- name: integer
|
||||
type: integer
|
||||
required: false
|
||||
label: Integer - Instance Count
|
||||
description: >-
|
||||
Integer field, min/max value are provided
|
||||
minValue: 1
|
||||
maxValue: 100
|
||||
helpText: Enter an integer value between 1 and 100
|
||||
|
||||
- name: adminPassword
|
||||
type: password
|
||||
required: false
|
||||
label: Password - Administrator password
|
||||
descriptionTitle: Passwords
|
||||
description: >-
|
||||
Requirements: at least one letter in each
|
||||
register, a number and a special character. Password length should be
|
||||
a minimum of 7 characters.
|
||||
|
||||
- name: recoveryPassword
|
||||
type: password
|
||||
required: false
|
||||
label: Recovery password
|
||||
|
||||
- bindedApps:
|
||||
fields:
|
||||
- name: DeployingApp
|
||||
type: io.murano.apps.DeployingApp
|
||||
required: false
|
||||
|
||||
- instanceConfiguration:
|
||||
fields:
|
||||
- name: flavor
|
||||
type: flavor
|
||||
label: Instance flavor
|
||||
required: false
|
||||
- name: osImage
|
||||
type: image
|
||||
imageType: linux
|
||||
label: Instance image
|
||||
- name: availabilityZone
|
||||
type: azone
|
||||
label: Availability zone
|
||||
required: false
|
||||
|
||||
|
|
@ -348,6 +348,12 @@ class PackageBase(UITestCase):
|
|||
cls.murano_client,
|
||||
"HotExample",
|
||||
{"tags": ["hot"]}, hot=True)
|
||||
cls.deployingapp_id = utils.upload_app_package(
|
||||
cls.murano_client,
|
||||
"DeployingApp",
|
||||
{"categories": ["Web"], "tags": ["tag"]},
|
||||
hot=False,
|
||||
package_dir=consts.DeployingPackageDir)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
@ -355,6 +361,7 @@ class PackageBase(UITestCase):
|
|||
cls.murano_client.packages.delete(cls.mockapp_id)
|
||||
cls.murano_client.packages.delete(cls.postgre_id)
|
||||
cls.murano_client.packages.delete(cls.hot_app_id)
|
||||
cls.murano_client.packages.delete(cls.deployingapp_id)
|
||||
|
||||
|
||||
class ImageTestCase(PackageBase):
|
||||
|
|
|
@ -4,6 +4,8 @@ PackageDir = os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
|||
'MockApp')
|
||||
HotPackageDir = os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
||||
'HotApp')
|
||||
DeployingPackageDir = os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
||||
'DeployingApp')
|
||||
|
||||
CategorySelector = "//a[contains(text(), '{0}')][contains(@class, 'dropdown-toggle')]" # noqa
|
||||
EnvAppsCategorySelector = "//*[contains(@id, 'envAppsCategoryBtn')]"
|
||||
|
@ -27,6 +29,7 @@ DatabaseCategory = "select[name='add_category-categories'] > option[value='Datab
|
|||
CategoryPackageCount = "//tr[contains(@data-display, '{0}')]/td[contains(text(), '{1}')]" # noqa
|
||||
Action = '//a[contains(@class, "murano_action") and contains(text(), "testAction")]' # noqa
|
||||
HotFlavorField = '//div[contains(@class, "has-error")]//input'
|
||||
EnvCheckbox = "//tr[contains(@data-display, '{0}')]/td[contains(@class, 'multi_select_column')]//div//label" # noqa
|
||||
|
||||
# Buttons
|
||||
ButtonSubmit = ".//*[@name='wizard_goto_step'][2]"
|
||||
|
@ -39,8 +42,11 @@ CreateEnvironment = ".add_env .btn"
|
|||
DeployEnvironment = "services__action_deploy_env"
|
||||
DeleteEnvironment = "//button[contains(@id, 'action_delete')]"
|
||||
DeployEnvironments = ".btn#environments__action_deploy"
|
||||
DeployEnvironmentsDisabled = ".btn#environments__action_deploy[disabled]"
|
||||
DeleteEnvironments = ".btn#environments__action_delete"
|
||||
DeleteEnvironmentsDisabled = ".btn#environments__action_delete[disabled]"
|
||||
AbandonEnvironments = ".btn#environments__action_abandon"
|
||||
AbandonEnvironmentsDisabled = ".btn#environments__action_abandon[disabled]"
|
||||
ConfirmCreateEnvironment = 'confirm_create_env'
|
||||
AddComponent = "services__action_AddApplication"
|
||||
AddCategory = "categories__action_add_category"
|
||||
|
|
|
@ -2151,8 +2151,8 @@ class TestSuiteMultipleEnvironments(base.ApplicationTestCase):
|
|||
4. Deploy created environments at once
|
||||
5. Abandon environments before they are deployed
|
||||
"""
|
||||
self.add_app_to_env(self.mockapp_id)
|
||||
self.add_app_to_env(self.mockapp_id)
|
||||
self.add_app_to_env(self.deployingapp_id)
|
||||
self.add_app_to_env(self.deployingapp_id)
|
||||
self.go_to_submenu('Environments')
|
||||
self.driver.find_element_by_css_selector(
|
||||
"label[for=ui-id-1]").click()
|
||||
|
@ -2167,3 +2167,100 @@ class TestSuiteMultipleEnvironments(base.ApplicationTestCase):
|
|||
self.wait_for_alert_message()
|
||||
self.check_element_not_on_page(by.By.LINK_TEXT, 'quick-env-1')
|
||||
self.check_element_not_on_page(by.By.LINK_TEXT, 'quick-env-2')
|
||||
|
||||
def test_check_necessary_buttons_are_available(self):
|
||||
"""Test check if the necessary buttons are available
|
||||
|
||||
Scenario:
|
||||
1. Create 4 environments with different statuses.
|
||||
2. Check that all action buttons are on page.
|
||||
3. Check that "Deploy Environments" button is only clickable
|
||||
if env with status "Ready to deploy" is checked
|
||||
4. Check that "Delete Environments" button is only clickable
|
||||
if envs with statuses "Ready", "Ready to deploy", "Ready to
|
||||
configure" are checked
|
||||
5. Check that "Abandon Environments" button is only clickable
|
||||
if env with status "Ready", "Deploying" are checked
|
||||
"""
|
||||
self.go_to_submenu('Environments')
|
||||
self.create_environment('quick-env-1')
|
||||
self.go_to_submenu('Environments')
|
||||
self.check_element_on_page(by.By.XPATH,
|
||||
c.EnvStatus.format('quick-env-1',
|
||||
'Ready to configure'))
|
||||
self.check_element_on_page(by.By.CSS_SELECTOR, c.DeleteEnvironments)
|
||||
|
||||
self.add_app_to_env(self.mockapp_id)
|
||||
self.go_to_submenu('Environments')
|
||||
self.check_element_on_page(by.By.XPATH,
|
||||
c.EnvStatus.format('quick-env-2',
|
||||
'Ready to deploy'))
|
||||
self.check_element_on_page(by.By.CSS_SELECTOR, c.DeployEnvironments)
|
||||
|
||||
self.add_app_to_env(self.mockapp_id)
|
||||
self.driver.find_element_by_id('services__action_deploy_env').click()
|
||||
self.check_element_on_page(by.By.XPATH,
|
||||
c.Status.format('Ready'),
|
||||
sec=90)
|
||||
self.go_to_submenu('Environments')
|
||||
self.check_element_on_page(by.By.XPATH,
|
||||
c.EnvStatus.format('quick-env-3', 'Ready'))
|
||||
self.check_element_on_page(by.By.CSS_SELECTOR, c.AbandonEnvironments)
|
||||
|
||||
self.add_app_to_env(self.deployingapp_id)
|
||||
self.driver.find_element_by_id('services__action_deploy_env').click()
|
||||
self.go_to_submenu('Environments')
|
||||
self.check_element_on_page(by.By.XPATH,
|
||||
c.EnvStatus.format('quick-env-4',
|
||||
'Deploying'))
|
||||
|
||||
env_1_checkbox = self.driver.find_element_by_xpath(
|
||||
c.EnvCheckbox.format('quick-env-1'))
|
||||
env_2_checkbox = self.driver.find_element_by_xpath(
|
||||
c.EnvCheckbox.format('quick-env-2'))
|
||||
env_3_checkbox = self.driver.find_element_by_xpath(
|
||||
c.EnvCheckbox.format('quick-env-3'))
|
||||
|
||||
# select 'Ready to deploy' env
|
||||
env_2_checkbox.click()
|
||||
# check that Deploy is possible
|
||||
self.check_element_on_page(by.By.CSS_SELECTOR, c.DeployEnvironments)
|
||||
self.check_element_not_on_page(by.By.CSS_SELECTOR,
|
||||
c.DeployEnvironmentsDisabled)
|
||||
|
||||
# add 'Ready to configure' env to selection
|
||||
env_1_checkbox.click()
|
||||
# check that Deploy is no more possible
|
||||
self.check_element_on_page(by.By.CSS_SELECTOR,
|
||||
c.DeployEnvironmentsDisabled)
|
||||
|
||||
# add 'Ready' env to selection
|
||||
env_3_checkbox.click()
|
||||
# check that Delete is possible
|
||||
self.check_element_on_page(by.By.CSS_SELECTOR, c.DeleteEnvironments)
|
||||
self.check_element_not_on_page(by.By.CSS_SELECTOR,
|
||||
c.DeleteEnvironmentsDisabled)
|
||||
|
||||
# add 'Deploying' env to selection
|
||||
env_4_checkbox = self.driver.find_element_by_xpath(
|
||||
c.EnvCheckbox.format('quick-env-4'))
|
||||
env_4_checkbox.click()
|
||||
# check that Delete is no more possible
|
||||
self.check_element_on_page(by.By.CSS_SELECTOR,
|
||||
c.DeleteEnvironmentsDisabled)
|
||||
|
||||
# check that Abandon is not possible
|
||||
self.check_element_on_page(by.By.CSS_SELECTOR,
|
||||
c.AbandonEnvironmentsDisabled)
|
||||
|
||||
# unselect all envs but 'Deploying' and 'Ready'
|
||||
env_1_checkbox.click()
|
||||
env_2_checkbox.click()
|
||||
# check that Abandon is now possible
|
||||
self.check_element_on_page(by.By.CSS_SELECTOR, c.AbandonEnvironments)
|
||||
self.check_element_not_on_page(by.By.CSS_SELECTOR,
|
||||
c.AbandonEnvironmentsDisabled)
|
||||
|
||||
self.check_element_on_page(by.By.XPATH,
|
||||
c.EnvStatus.format('quick-env-4', 'Ready'),
|
||||
sec=90)
|
||||
|
|
|
@ -31,11 +31,12 @@ class ImageException(Exception):
|
|||
return self._error_string
|
||||
|
||||
|
||||
def upload_app_package(client, app_name, data, hot=False):
|
||||
def upload_app_package(client, app_name, data, hot=False,
|
||||
package_dir=consts.PackageDir):
|
||||
try:
|
||||
if not hot:
|
||||
manifest = os.path.join(consts.PackageDir, 'manifest.yaml')
|
||||
archive = compose_package(app_name, manifest, consts.PackageDir)
|
||||
manifest = os.path.join(package_dir, 'manifest.yaml')
|
||||
archive = compose_package(app_name, manifest, package_dir)
|
||||
else:
|
||||
manifest = os.path.join(consts.HotPackageDir, 'manifest.yaml')
|
||||
archive = compose_package(app_name, manifest,
|
||||
|
|
Loading…
Reference in New Issue