diff --git a/app/controllers/ops_controller.rb b/app/controllers/ops_controller.rb
index 447d2feca4d..0318d595bd2 100644
--- a/app/controllers/ops_controller.rb
+++ b/app/controllers/ops_controller.rb
@@ -583,7 +583,9 @@ def replace_right_cell(options = {})
replace_explorer_trees(replace_trees, presenter)
rebuild_toolbars(presenter)
- handle_bottom_cell(nodetype, presenter, locals)
+ unless @hide_bottom_bar
+ handle_bottom_cell(nodetype, presenter, locals)
+ end
x_active_tree_replace_cell(nodetype, presenter)
extra_js_commands(presenter)
diff --git a/app/controllers/ops_controller/ops_rbac.rb b/app/controllers/ops_controller/ops_rbac.rb
index fb1a370aaff..096fb75d190 100644
--- a/app/controllers/ops_controller/ops_rbac.rb
+++ b/app/controllers/ops_controller/ops_rbac.rb
@@ -51,13 +51,15 @@ def rbac_tags_edit
def rbac_user_add
assert_privileges("rbac_user_add")
+ @hide_bottom_bar = true
rbac_edit_reset('new', 'user', User)
end
def rbac_user_copy
# get users id either from gtl check or detail id
user_id = params[:miq_grid_checks].presence || params[:id]
- user = User.find(user_id)
+ user = User.find(user_id)
+
# check if it is allowed to copy the user
if rbac_user_copy_restriction?(user)
rbac_restricted_user_copy_flash(user)
@@ -66,17 +68,17 @@ def rbac_user_copy
javascript_flash
return
end
+
+ @copy_user_id = user_id
assert_privileges("rbac_user_copy")
+ @hide_bottom_bar = true
rbac_edit_reset('copy', 'user', User)
end
def rbac_user_edit
assert_privileges("rbac_user_edit")
- case params[:button]
- when 'cancel' then rbac_edit_cancel('user')
- when 'save', 'add' then rbac_edit_save_or_add('user')
- when 'reset', nil then rbac_edit_reset(params[:typ], 'user', User) # Reset or first time in
- end
+ @hide_bottom_bar = true
+ rbac_edit_reset(params[:typ], 'user', User)
end
def rbac_group_add
@@ -185,13 +187,6 @@ def rbac_tenant_tags_edit
end
end
- # AJAX driven routines to check for changes in ANY field on the form
- def rbac_user_field_changed
- assert_privileges(params[:id] == "new" ? "rbac_user_add" : "rbac_user_edit")
-
- rbac_field_changed("user")
- end
-
def rbac_group_field_changed
assert_privileges(params[:id] == "new" ? "rbac_group_add" : "rbac_group_edit")
@@ -660,10 +655,6 @@ def rbac_edit_save_or_add(what, rbac_suffix = what)
return unless load_edit("rbac_#{what}_edit__#{id}", "replace_cell__explorer")
case key
- when :user
- record = @edit[:user_id] ? User.find_by(:id => @edit[:user_id]) : User.new
- validated = rbac_user_validate?
- rbac_user_set_record_vars(record)
when :group then
record = @edit[:group_id] ? MiqGroup.find_by(:id => @edit[:group_id]) : MiqGroup.new
validated = rbac_group_validate?
@@ -676,7 +667,6 @@ def rbac_edit_save_or_add(what, rbac_suffix = what)
end
if record.valid? && validated && record.save!
- record.update!(:miq_groups => Rbac.filtered(MiqGroup.where(:id => rbac_user_get_group_ids))) if key == :user # only set miq_groups if everything is valid
populate_role_features(record) if what == "role"
self.current_user = record if what == 'user' && @edit[:current][:userid] == current_userid
AuditEvent.success(build_saved_audit(record, @edit))
@@ -746,13 +736,10 @@ def rbac_field_changed(rec_type)
return unless load_edit("rbac_#{rec_type}_edit__#{id}", "replace_cell__explorer")
case rec_type
- when "user" then rbac_user_get_form_vars
when "group" then rbac_group_get_form_vars
when "role" then rbac_role_get_form_vars
end
- @edit[:new][:group] = rbac_user_get_group_ids if rec_type == "user"
-
session[:changed] = changed = @edit[:new] != @edit[:current]
render :update do |page|
@@ -764,11 +751,6 @@ def rbac_field_changed(rec_type)
page.replace(@refresh_div, :partial => @refresh_partial)
end
else
- # only do following for user (adding/editing a user)
- if x_node.split("-").first == "u" || x_node == "xx-u"
- page.replace("group_selected",
- :partial => "ops/rbac_group_selected")
- end
# only do following for groups
if @refresh_div
page.replace(@refresh_div,
@@ -966,25 +948,9 @@ def build_rbac_feature_tree
# Set form variables for user edit
def rbac_user_set_form_vars
copy = @sb[:typ] == "copy"
- # save a shadow copy of the record if record is being copied
- @user = copy ? @record.dup : @record
- @user.miq_groups = @record.miq_groups if copy
@edit = {:new => {}, :current => {}}
@edit[:user_id] = @record.id unless copy
- @edit[:key] = "rbac_user_edit__#{@edit[:user_id] || "new"}"
- # prefill form fields for edit and copy action
- @edit[:new].merge!(:name => @user.name,
- :email => @user.email,
- :group => @user.miq_groups ? @user.miq_groups.map(&:id).sort : nil)
- unless copy
- @edit[:new].merge!(:userid => @user.userid,
- :password => @user.password,
- :verify => @user.password)
- end
- # load all user groups, filter available for tenant
- @edit[:groups] = Rbac.filtered(MiqGroup.non_tenant_groups_in_my_region).sort_by { |g| g.description.downcase }.collect { |g| [g.description, g.id] }
- # store current state of the new users information
- @edit[:current] = copy_hash(@edit[:new])
+ @edit[:new][:userid] = @record.userid
@right_cell_text = if @edit[:user_id]
_("Editing User \"%{name}\"") % {:name => @record.name}
else
@@ -992,56 +958,6 @@ def rbac_user_set_form_vars
end
end
- # Get variables from user edit form
- def rbac_user_get_form_vars
- copy_params_if_present(@edit[:new], params, %i[name password verify])
-
- @edit[:new][:userid] = params[:userid].strip.presence if params[:userid]
- @edit[:new][:email] = params[:email].strip.presence if params[:email]
- @edit[:new][:group] = params[:chosen_group] if params[:chosen_group]
- end
-
- # Set user record variables to new values
- def rbac_user_set_record_vars(user)
- user.name = @edit[:new][:name]
- user.userid = @edit[:new][:userid]
- user.email = @edit[:new][:email]
- user.password = @edit[:new][:password] if @edit[:new][:password]
- end
-
- # Get array of group ids
- def rbac_user_get_group_ids
- case @edit[:new][:group]
- when 'null', nil
- []
- when String
- @edit[:new][:group].split(',').delete_if(&:blank?).map(&:to_i).sort
- when Array
- @edit[:new][:group].map(&:to_i).sort
- end
- end
-
- # Validate some of the user fields
- def rbac_user_validate?
- valid = true
- if @edit[:new][:password] != @edit[:new][:verify]
- add_flash(_("Password/Verify Password do not match"), :error)
- valid = false
- end
-
- new_group_ids = rbac_user_get_group_ids
- new_groups = new_group_ids.present? && MiqGroup.find(new_group_ids).present? ? MiqGroup.find(new_group_ids) : []
-
- if new_group_ids.blank?
- add_flash(_("A User must be assigned to a Group"), :error)
- valid = false
- elsif Rbac.filtered(new_groups).count != new_group_ids.count
- add_flash(_("A User must be assigned to an allowed Group"), :error)
- valid = false
- end
- valid
- end
-
def valid_tenant?(tenant_id)
Rbac.filtered(Tenant.in_my_region.where(:id => tenant_id)).present?
end
diff --git a/app/javascript/components/async-credentials/edit-password-field.jsx b/app/javascript/components/async-credentials/edit-password-field.jsx
index 355f63521dc..18720794fd7 100644
--- a/app/javascript/components/async-credentials/edit-password-field.jsx
+++ b/app/javascript/components/async-credentials/edit-password-field.jsx
@@ -10,7 +10,7 @@ import { useFieldApi, componentTypes } from '@@ddf';
const EditPasswordField = ({ componentClass, ...props }) => {
const {
- labelText, validateOnMount, isDisabled, editMode, setEditMode, buttonLabel, input, meta, ...rest
+ labelText, validateOnMount, isDisabled, editMode, setEditMode, buttonLabel, input, meta, icon, kind, ...rest
} = useFieldApi(prepareProps(props));
const invalid = (meta.touched || validateOnMount) && meta.error;
@@ -58,7 +58,14 @@ const EditPasswordField = ({ componentClass, ...props }) => {
{ field }
-
+
diff --git a/app/javascript/components/selected-groups-list/index.jsx b/app/javascript/components/selected-groups-list/index.jsx
new file mode 100644
index 00000000000..458ffdec3c0
--- /dev/null
+++ b/app/javascript/components/selected-groups-list/index.jsx
@@ -0,0 +1,43 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const SelectedGroupsList = ({ groups }) => {
+ const selectedGroups = [];
+ // console.log(groups);
+ groups.sort();
+ if (groups) {
+ groups.forEach((group) => {
+ selectedGroups.push(
+
+ );
+ });
+ }
+
+ return (
+
+
+
+ {selectedGroups}
+
+
+ );
+};
+
+SelectedGroupsList.propTypes = {
+ groups: PropTypes.arrayOf(PropTypes.string),
+};
+
+SelectedGroupsList.defaultProps = {
+ groups: [],
+};
+
+export default SelectedGroupsList;
diff --git a/app/javascript/components/user-form/helper.js b/app/javascript/components/user-form/helper.js
new file mode 100644
index 00000000000..4c494d77b9a
--- /dev/null
+++ b/app/javascript/components/user-form/helper.js
@@ -0,0 +1,54 @@
+const areGroupsEqual = (initialValues, selectedGroups = []) => {
+ selectedGroups.sort();
+ initialValues.groups.sort();
+ if (selectedGroups.length !== initialValues.groups.length) {
+ return false;
+ }
+ return selectedGroups.every((group, index) => group === initialValues.groups[index]);
+};
+
+export const passwordValidation = (initialValues, id, editMode, values, setState, selectedGroups) => {
+ if (values.groups === undefined) {
+ if (selectedGroups.length > 0) {
+ setState((state) => ({
+ ...state,
+ selectedGroups: [],
+ }));
+ }
+ }
+ const errors = {};
+ const groupIds = [];
+ if (values.groups) {
+ values.groups.forEach((group) => {
+ if (group.value) {
+ groupIds.push(group.value);
+ } else {
+ groupIds.push(group);
+ }
+ });
+ }
+ if (!editMode && !!id) {
+ values.password = undefined;
+ values.confirmPassword = undefined;
+ if (values.name === initialValues.name
+ && values.userid === initialValues.userid
+ && values.email === initialValues.email
+ && areGroupsEqual(initialValues, groupIds)) {
+ errors.confirmPassword = '';
+ }
+ }
+
+ if (values.password === undefined) {
+ if (!!id && editMode) {
+ errors.password = 'Required';
+ }
+ }
+
+ if (editMode && values.password !== values.confirmPassword) {
+ errors.confirmPassword = 'Password/Verify Password do not match';
+ }
+ if (!!id === false && values.password !== values.confirmPassword) {
+ errors.confirmPassword = 'Password/Verify Password do not match';
+ }
+ return errors;
+};
diff --git a/app/javascript/components/user-form/index.jsx b/app/javascript/components/user-form/index.jsx
new file mode 100644
index 00000000000..86d345cf8f7
--- /dev/null
+++ b/app/javascript/components/user-form/index.jsx
@@ -0,0 +1,200 @@
+import React, { useState, useEffect } from 'react';
+import PropTypes from 'prop-types';
+import MiqFormRenderer from '@@ddf';
+import { Loading } from 'carbon-components-react';
+import createSchema from './user-form.schema';
+import miqRedirectBack from '../../helpers/miq-redirect-back';
+import { API } from '../../http_api';
+import { passwordValidation } from './helper';
+
+const UserForm = ({
+ id, copyUserId, disabled, dbMode,
+}) => {
+ const [{
+ initialValues, isLoading, editMode, groups, selectedGroups, // Use to populate the custom component
+ }, setState] = useState({ isLoading: true });
+
+ useEffect(() => {
+ if (id) {
+ Promise.all([API.get('/api/groups?&expand=resources'), API.get(`/api/users/${id}?&attributes=miq_groups`)])
+ .then(([{ resources }, userData]) => {
+ const availableGroups = [];
+ resources.forEach((group) => {
+ availableGroups.push({ label: group.description, value: group.id });
+ });
+
+ const selectedGroups = [];
+ const selectedGroupIds = [];
+ userData.miq_groups.forEach((group) => {
+ selectedGroups.push(group.description);
+ selectedGroupIds.push(group.id);
+ });
+ userData.groups = selectedGroupIds;
+ userData.selectedGroups = selectedGroups;
+ setState({
+ initialValues: userData,
+ isLoading: false,
+ editMode: false,
+ groups: availableGroups,
+ selectedGroups,
+ });
+ });
+ } else {
+ const promises = [API.get('/api/groups?&expand=resources')];
+ if (copyUserId) {
+ promises.push(API.get(`/api/users/${copyUserId}?&attributes=miq_groups`));
+ }
+ Promise.all(promises).then(([{ resources }, userData]) => {
+ const availableGroups = [];
+ resources.forEach((group) => {
+ availableGroups.push({ label: group.description, value: group.id });
+ });
+
+ const values = {};
+ const selectedGroups = [];
+ const selectedGroupIds = [];
+ if (userData) {
+ if (userData.name) {
+ values.name = userData.name;
+ }
+ if (userData.email) {
+ values.email = userData.email;
+ }
+ if (userData.miq_groups) {
+ userData.miq_groups.forEach((group) => {
+ selectedGroups.push(group.description);
+ selectedGroupIds.push(group.id);
+ });
+ values.groups = selectedGroupIds;
+ values.selectedGroups = selectedGroups;
+ }
+ }
+
+ setState({
+ initialValues: values,
+ isLoading: false,
+ editMode: false,
+ groups: availableGroups,
+ selectedGroups: [],
+ });
+ });
+ }
+ }, []);
+
+ const onSubmit = (values) => {
+ miqSparkleOn();
+ if (values.email === undefined) {
+ values.email = '';
+ }
+ const groupIds = new Set();
+ const sortedGroupIds = [];
+ const groupIdObjects = [];
+ values.groups.forEach((group) => {
+ if (group.value) {
+ groupIds.add(group.value);
+ } else {
+ groupIds.add(group);
+ }
+ });
+ groupIds.forEach((group) => {
+ sortedGroupIds.push(group);
+ });
+ sortedGroupIds.sort();
+ sortedGroupIds.forEach((group) => {
+ groupIdObjects.push({ id: group });
+ });
+ if (id) {
+ if (values.confirmPassword && values.confirmPassword === values.password) {
+ API.post(`/api/users/${id}`,
+ {
+ action: 'edit',
+ resource: {
+ name: values.name,
+ userid: values.userid,
+ password: values.password,
+ email: values.email,
+ miq_groups: groupIdObjects,
+ },
+ }).then(() => {
+ miqRedirectBack(`User ${values.name} was saved`, 'success', '/ops/');
+ });
+ } else {
+ API.post(`/api/users/${id}`,
+ {
+ action: 'edit',
+ resource: {
+ name: values.name,
+ userid: values.userid,
+ email: values.email,
+ miq_groups: groupIdObjects,
+ },
+ }).then(() => {
+ miqRedirectBack(`User ${values.name} was saved`, 'success', '/ops/');
+ });
+ }
+ miqSparkleOff();
+ } else {
+ API.post('/api/users', {
+ name: values.name,
+ userid: values.userid,
+ password: values.password,
+ email: values.email,
+ miq_groups: groupIdObjects,
+ }).then(() => {
+ miqRedirectBack(`User ${values.name} was saved`, 'success', '/ops/');
+ });
+ miqSparkleOff();
+ }
+ };
+
+ const onCancel = () => {
+ const url = '/ops/';
+ let message = '';
+ message = __('Add of new User was cancelled by the user');
+ miqRedirectBack(message, 'success', url);
+ };
+
+ const onFormReset = () => {
+ const temp = initialValues;
+ temp.selectedGroups = selectedGroups;
+ setState((state) => ({
+ ...state,
+ initialValues: temp,
+ editMode: false,
+ }));
+ add_flash(__('All changes have been reset'), 'warn');
+ };
+
+ if (isLoading) return ;
+ return !isLoading && (
+
+ passwordValidation(initialValues, id, editMode, values, setState, selectedGroups)}
+ onSubmit={onSubmit}
+ onCancel={onCancel}
+ canReset={!!id}
+ onReset={onFormReset}
+ buttonsLabels={{
+ submitLabel: id ? __('Submit') : __('Add'),
+ }}
+ />
+
+ );
+};
+
+UserForm.propTypes = {
+ id: PropTypes.number,
+ copyUserId: PropTypes.number,
+ disabled: PropTypes.bool,
+ dbMode: PropTypes.string.isRequired,
+};
+
+UserForm.defaultProps = {
+ id: undefined,
+ copyUserId: undefined,
+ disabled: false,
+};
+
+export default UserForm;
diff --git a/app/javascript/components/user-form/user-form.schema.js b/app/javascript/components/user-form/user-form.schema.js
new file mode 100644
index 00000000000..2545a90af67
--- /dev/null
+++ b/app/javascript/components/user-form/user-form.schema.js
@@ -0,0 +1,167 @@
+import { componentTypes, validatorTypes } from '@@ddf';
+import { Edit16 } from '@carbon/icons-react';
+
+function createSchema(id, editMode, setState, disabled, dbMode, availableGroups, selectedGroups) {
+ const fields = [
+ {
+ component: componentTypes.TEXT_FIELD,
+ label: __('Full Name'),
+ maxLength: 50,
+ id: 'name',
+ name: 'name',
+ isDisabled: disabled,
+ isRequired: true,
+ validate: [{
+ type: validatorTypes.REQUIRED,
+ message: __('Required'),
+ }],
+ },
+ {
+ component: componentTypes.TEXT_FIELD,
+ label: __('Username'),
+ maxLength: 255,
+ id: 'userid',
+ name: 'userid',
+ isDisabled: disabled,
+ isRequired: true,
+ validate: [{
+ type: validatorTypes.REQUIRED,
+ message: __('Required'),
+ }],
+ },
+ ...(dbMode === 'database' || (dbMode !== 'database' && disabled) ? [
+ ...(id ? [
+ ...(editMode ? [
+ {
+ component: 'edit-password-field',
+ label: __('Password'),
+ id: 'password',
+ name: 'password',
+ maxLength: 50,
+ editMode,
+ disabled: !editMode,
+ setEditMode: () => {
+ setState((state) => ({
+ ...state,
+ editMode: !editMode,
+ }));
+ },
+ placeholder: '●●●●●●●●',
+ buttonLabel: editMode ? __('Cancel') : __('Change'),
+ },
+ {
+ component: componentTypes.TEXT_FIELD,
+ label: __('Confirm Password'),
+ maxLength: 50,
+ type: 'password',
+ id: 'confirmPassword',
+ name: 'confirmPassword',
+ initialValue: '',
+ isRequired: true,
+ },
+ ] : [
+ {
+ component: 'edit-password-field',
+ label: __('Password'),
+ maxLength: 50,
+ id: 'passwordPlaceholder',
+ name: 'passwordPlaceholder',
+ icon: Edit16,
+ kind: 'primary',
+ editMode,
+ disabled: true,
+ setEditMode: () => {
+ setState((state) => ({
+ ...state,
+ editMode: !editMode,
+ }));
+ },
+ placeholder: '●●●●●●●●',
+ buttonLabel: editMode ? __('Cancel') : __('Change'),
+ },
+ ]),
+ ] : [
+ {
+ component: componentTypes.TEXT_FIELD,
+ label: __('Password'),
+ maxLength: 50,
+ type: 'password',
+ id: 'password',
+ name: 'password',
+ isRequired: true,
+ validate: [{
+ type: validatorTypes.REQUIRED,
+ message: __('Required'),
+ }],
+ },
+ {
+ component: componentTypes.TEXT_FIELD,
+ label: __('Confirm Password'),
+ maxLength: 50,
+ type: 'password',
+ id: 'confirmPassword',
+ name: 'confirmPassword',
+ initialValue: '',
+ isRequired: true,
+ validate: [{
+ type: validatorTypes.REQUIRED,
+ message: __('Required'),
+ }],
+ },
+ ])] : []),
+ {
+ component: componentTypes.TEXT_FIELD,
+ label: __('E-mail Address'),
+ maxLength: 255,
+ id: 'email',
+ name: 'email',
+ validate: [{
+ type: validatorTypes.PATTERN,
+ pattern: '[a-z0-9._%+-]+@[a-z0-9.-]+.[a-z]{2,}$',
+ message: __('Email must be a valid email address'),
+ }],
+ },
+ {
+ component: componentTypes.SELECT,
+ label: __('Available Groups'),
+ id: 'groups',
+ name: 'groups',
+ isDisabled: disabled,
+ isMulti: true,
+ isRequired: true,
+ placeholder: __(''),
+ options: availableGroups,
+ validate: [{
+ type: validatorTypes.REQUIRED,
+ message: __('Required'),
+ }],
+ onChange: (values) => {
+ const groups = [];
+ values.forEach((group) => {
+ if (group.label) {
+ groups.push(group.label);
+ } else {
+ availableGroups.forEach((availableGroup) => {
+ if (availableGroup.value === group) {
+ groups.push(availableGroup.label);
+ }
+ });
+ }
+ });
+ setState((state) => ({
+ ...state,
+ selectedGroups: groups,
+ }));
+ },
+ },
+ {
+ component: 'selected-groups-list',
+ label: __('Selected Groups'),
+ name: 'selected-groups',
+ groups: selectedGroups,
+ },
+ ];
+ return { fields };
+}
+
+export default createSchema;
diff --git a/app/javascript/forms/mappers/componentMapper.jsx b/app/javascript/forms/mappers/componentMapper.jsx
index 70cce28946c..f1c84a665a7 100644
--- a/app/javascript/forms/mappers/componentMapper.jsx
+++ b/app/javascript/forms/mappers/componentMapper.jsx
@@ -13,6 +13,7 @@ import FontIconPickerDdf from '../../components/fonticon-picker/font-icon-picker
import KeyValueListComponent from '../../components/key-value-list';
import EmbeddedAutomateEntryPoint from '../../components/embedded-automate-entry-point';
import EmbeddedWorkflowEntryPoint from '../../components/embedded-workflow-entry-point';
+import SelectedGroupsList from '../../components/selected-groups-list';
const mapper = {
...componentMapper,
@@ -24,6 +25,7 @@ const mapper = {
'key-value-list': KeyValueListComponent,
'password-field': PasswordField,
'validate-credentials': AsyncCredentials,
+ 'selected-groups-list': SelectedGroupsList,
'tree-view': TreeViewField,
'tree-selector': TreeViewSelector,
[componentTypes.SELECT]: Select,
diff --git a/app/javascript/packs/component-definitions-common.js b/app/javascript/packs/component-definitions-common.js
index 1b3406d55f9..29c1798510d 100644
--- a/app/javascript/packs/component-definitions-common.js
+++ b/app/javascript/packs/component-definitions-common.js
@@ -154,6 +154,7 @@ import TimeProfileReportsTable from '../components/data-tables/time-profile-repo
import TimeProfileTable from '../components/data-tables/time-profile-table';
import ToastList from '../components/toast-list/toast-list';
import UsageTrendChart from '../components/provider-dashboard-charts/usage-network-image-charts';
+import UserForm from '../components/user-form';
import UtilizationChartGraph from '../components/provider-dashboard-charts/provider-dashboard-utilization-chart';
import VisualSettingsForm from '../components/visual-settings-form';
import VmCommonRenameForm from '../components/vm-common-rename-form';
@@ -342,6 +343,7 @@ ManageIQ.component.addReact('ToastList', ToastList);
ManageIQ.component.addReact('Toolbar', Toolbar);
ManageIQ.component.addReact('TreeViewRedux', TreeViewRedux);
ManageIQ.component.addReact('UsageTrendChart', UsageTrendChart);
+ManageIQ.component.addReact('UserForm', UserForm);
ManageIQ.component.addReact('UtilizationChartGraph', UtilizationChartGraph);
ManageIQ.component.addReact('VisualSettingsForm', VisualSettingsForm);
ManageIQ.component.addReact('VmCommonRenameForm', VmCommonRenameForm);
diff --git a/app/javascript/spec/action-form/__snapshots__/action-form.spec.js.snap b/app/javascript/spec/action-form/__snapshots__/action-form.spec.js.snap
index 9d123c24579..89ace785460 100644
--- a/app/javascript/spec/action-form/__snapshots__/action-form.spec.js.snap
+++ b/app/javascript/spec/action-form/__snapshots__/action-form.spec.js.snap
@@ -832,6 +832,7 @@ exports[`Action Form Component should render adding a new action 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -1546,6 +1547,7 @@ exports[`Action Form Component should render adding a new action 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/add-remove-security-groups-form/__snapshots__/add-remove-security-groups-form.spec.js.snap b/app/javascript/spec/add-remove-security-groups-form/__snapshots__/add-remove-security-groups-form.spec.js.snap
index 72d8afdddbf..e6304bd9e04 100644
--- a/app/javascript/spec/add-remove-security-groups-form/__snapshots__/add-remove-security-groups-form.spec.js.snap
+++ b/app/javascript/spec/add-remove-security-groups-form/__snapshots__/add-remove-security-groups-form.spec.js.snap
@@ -146,6 +146,7 @@ exports[`Add/remove security groups form component should remove security group
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -212,6 +213,7 @@ exports[`Add/remove security groups form component should remove security group
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/ansible-credentials-form/__snapshots__/ansible-credentials-form.spec.js.snap b/app/javascript/spec/ansible-credentials-form/__snapshots__/ansible-credentials-form.spec.js.snap
index bf8f651ffc5..a95ae56da72 100644
--- a/app/javascript/spec/ansible-credentials-form/__snapshots__/ansible-credentials-form.spec.js.snap
+++ b/app/javascript/spec/ansible-credentials-form/__snapshots__/ansible-credentials-form.spec.js.snap
@@ -106,6 +106,7 @@ exports[`Ansible Credential Form Component should render adding a new credential
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -202,6 +203,7 @@ exports[`Ansible Credential Form Component should render adding a new credential
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -1595,6 +1597,7 @@ exports[`Ansible Credential Form Component should render editing a credential 1`
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -1698,6 +1701,7 @@ exports[`Ansible Credential Form Component should render editing a credential 1`
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/ansible-edit-catalog-form/__snapshots__/ansible-edit-catalog-form.spec.js.snap b/app/javascript/spec/ansible-edit-catalog-form/__snapshots__/ansible-edit-catalog-form.spec.js.snap
index d046f049e1a..982247af4bb 100644
--- a/app/javascript/spec/ansible-edit-catalog-form/__snapshots__/ansible-edit-catalog-form.spec.js.snap
+++ b/app/javascript/spec/ansible-edit-catalog-form/__snapshots__/ansible-edit-catalog-form.spec.js.snap
@@ -1323,6 +1323,7 @@ exports[`Ansible playbook edit catalog Form Component should not render some fie
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -3535,6 +3536,7 @@ exports[`Ansible playbook edit catalog Form Component should not render some fie
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -5754,6 +5756,7 @@ exports[`Ansible playbook edit catalog Form Component should not render some fie
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -59566,6 +59569,7 @@ exports[`Ansible playbook edit catalog Form Component should render correct form
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -61787,6 +61791,7 @@ exports[`Ansible playbook edit catalog Form Component should render correct form
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -64015,6 +64020,7 @@ exports[`Ansible playbook edit catalog Form Component should render correct form
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -120752,6 +120758,7 @@ exports[`Ansible playbook edit catalog Form Component should render retirement p
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -122964,6 +122971,7 @@ exports[`Ansible playbook edit catalog Form Component should render retirement p
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -125183,6 +125191,7 @@ exports[`Ansible playbook edit catalog Form Component should render retirement p
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/c-and-u-collections-form/__snapshots__/c-and-u-collections-form.spec.js.snap b/app/javascript/spec/c-and-u-collections-form/__snapshots__/c-and-u-collections-form.spec.js.snap
index bd5a0a74490..7b027908866 100644
--- a/app/javascript/spec/c-and-u-collections-form/__snapshots__/c-and-u-collections-form.spec.js.snap
+++ b/app/javascript/spec/c-and-u-collections-form/__snapshots__/c-and-u-collections-form.spec.js.snap
@@ -112,6 +112,7 @@ exports[`DiagnosticsCURepairForm Component Should add a record from DiagnosticsC
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -224,6 +225,7 @@ exports[`DiagnosticsCURepairForm Component Should add a record from DiagnosticsC
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/cloud-database-form/__snapshots__/cloud-database-form.spec.js.snap b/app/javascript/spec/cloud-database-form/__snapshots__/cloud-database-form.spec.js.snap
index 565a9d709b6..54afbbbbc2b 100644
--- a/app/javascript/spec/cloud-database-form/__snapshots__/cloud-database-form.spec.js.snap
+++ b/app/javascript/spec/cloud-database-form/__snapshots__/cloud-database-form.spec.js.snap
@@ -144,6 +144,7 @@ exports[`Cloud Database form component should render "Edit" form 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -229,6 +230,7 @@ exports[`Cloud Database form component should render "Edit" form 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/cloud-object-store-container-form/__snapshots__/cloud-object-store-container-form.spec.js.snap b/app/javascript/spec/cloud-object-store-container-form/__snapshots__/cloud-object-store-container-form.spec.js.snap
index cda9a34a03e..17458b29449 100644
--- a/app/javascript/spec/cloud-object-store-container-form/__snapshots__/cloud-object-store-container-form.spec.js.snap
+++ b/app/javascript/spec/cloud-object-store-container-form/__snapshots__/cloud-object-store-container-form.spec.js.snap
@@ -76,6 +76,7 @@ exports[`Cloud Object Store Container form component should add Amazon cloud obj
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -143,6 +144,7 @@ exports[`Cloud Object Store Container form component should add Amazon cloud obj
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -998,6 +1000,7 @@ exports[`Cloud Object Store Container form component should add Openstack cloud
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -1065,6 +1068,7 @@ exports[`Cloud Object Store Container form component should add Openstack cloud
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -1896,6 +1900,7 @@ exports[`Cloud Object Store Container form component should render add cloud obj
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -1963,6 +1968,7 @@ exports[`Cloud Object Store Container form component should render add cloud obj
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/cloud-volume-actions-form/__snapshots__/cloud-volume-actions-form.spec.js.snap b/app/javascript/spec/cloud-volume-actions-form/__snapshots__/cloud-volume-actions-form.spec.js.snap
index aaa7be01fdd..fdcae04c002 100644
--- a/app/javascript/spec/cloud-volume-actions-form/__snapshots__/cloud-volume-actions-form.spec.js.snap
+++ b/app/javascript/spec/cloud-volume-actions-form/__snapshots__/cloud-volume-actions-form.spec.js.snap
@@ -108,6 +108,7 @@ exports[`Cloud Volume Backup Create form component should render the cloud volum
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -203,6 +204,7 @@ exports[`Cloud Volume Backup Create form component should render the cloud volum
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -1652,6 +1654,7 @@ exports[`Cloud Volume Backup Create form component when adding a new backup of c
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -1747,6 +1750,7 @@ exports[`Cloud Volume Backup Create form component when adding a new backup of c
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -3176,6 +3180,7 @@ exports[`Cloud Volume Restore from backup form component should render the cloud
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -3251,6 +3256,7 @@ exports[`Cloud Volume Restore from backup form component should render the cloud
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -4273,6 +4279,7 @@ exports[`Cloud Volume Restore from backup form component when restoring cloud vo
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -4348,6 +4355,7 @@ exports[`Cloud Volume Restore from backup form component when restoring cloud vo
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -5362,6 +5370,7 @@ exports[`Cloud Volume Snapshot Create form component should render the cloud vol
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -5429,6 +5438,7 @@ exports[`Cloud Volume Snapshot Create form component should render the cloud vol
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -6217,6 +6227,7 @@ exports[`Cloud Volume Snapshot Create form component when adding a new snapshot
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -6284,6 +6295,7 @@ exports[`Cloud Volume Snapshot Create form component when adding a new snapshot
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/cloud-volume-form/__snapshots__/attach-detach-cloud-volume-form.spec.js.snap b/app/javascript/spec/cloud-volume-form/__snapshots__/attach-detach-cloud-volume-form.spec.js.snap
index bc59b82ebdd..884d1cc772a 100644
--- a/app/javascript/spec/cloud-volume-form/__snapshots__/attach-detach-cloud-volume-form.spec.js.snap
+++ b/app/javascript/spec/cloud-volume-form/__snapshots__/attach-detach-cloud-volume-form.spec.js.snap
@@ -139,6 +139,7 @@ exports[`Attach / Detach form component should render Attach Cloud Volume to the
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -235,6 +236,7 @@ exports[`Attach / Detach form component should render Attach Cloud Volume to the
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -1406,6 +1408,7 @@ exports[`Attach / Detach form component should render Attach Selected Cloud Volu
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -1502,6 +1505,7 @@ exports[`Attach / Detach form component should render Attach Selected Cloud Volu
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -2660,6 +2664,7 @@ exports[`Attach / Detach form component should render Detach Cloud Volume from t
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -2743,6 +2748,7 @@ exports[`Attach / Detach form component should render Detach Cloud Volume from t
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -3707,6 +3713,7 @@ exports[`Attach / Detach form component should render Detach Selected Cloud Volu
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -3790,6 +3797,7 @@ exports[`Attach / Detach form component should render Detach Selected Cloud Volu
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -4769,6 +4777,7 @@ exports[`Attach / Detach form component should submit Attach API call 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -4865,6 +4874,7 @@ exports[`Attach / Detach form component should submit Attach API call 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -6023,6 +6033,7 @@ exports[`Attach / Detach form component should submit Detach API call 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -6106,6 +6117,7 @@ exports[`Attach / Detach form component should submit Detach API call 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/data-store-fore/__snapshots__/datastore-form.spec.js.snap b/app/javascript/spec/data-store-fore/__snapshots__/datastore-form.spec.js.snap
index caeb23bc7c3..048385dd71a 100644
--- a/app/javascript/spec/data-store-fore/__snapshots__/datastore-form.spec.js.snap
+++ b/app/javascript/spec/data-store-fore/__snapshots__/datastore-form.spec.js.snap
@@ -181,6 +181,7 @@ exports[`Datastore form component Datastore domain form component should render
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -280,6 +281,7 @@ exports[`Datastore form component Datastore domain form component should render
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -1773,6 +1775,7 @@ exports[`Datastore form component Datastore namespace form component should rend
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -1875,6 +1878,7 @@ exports[`Datastore form component Datastore namespace form component should rend
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/embedded-terraform-credentials-form/__snapshots__/embedded-terraform-credentials-form.spec.js.snap b/app/javascript/spec/embedded-terraform-credentials-form/__snapshots__/embedded-terraform-credentials-form.spec.js.snap
index cde9915dbab..dd42a6a44f5 100644
--- a/app/javascript/spec/embedded-terraform-credentials-form/__snapshots__/embedded-terraform-credentials-form.spec.js.snap
+++ b/app/javascript/spec/embedded-terraform-credentials-form/__snapshots__/embedded-terraform-credentials-form.spec.js.snap
@@ -107,6 +107,7 @@ exports[`Embedded Terraform Credential Form Component should render adding a new
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -204,6 +205,7 @@ exports[`Embedded Terraform Credential Form Component should render adding a new
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -1618,6 +1620,7 @@ exports[`Embedded Terraform Credential Form Component should render editing a cr
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -1722,6 +1725,7 @@ exports[`Embedded Terraform Credential Form Component should render editing a cr
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/evacuate-form/__snapshots__/evacuate-form.spec.js.snap b/app/javascript/spec/evacuate-form/__snapshots__/evacuate-form.spec.js.snap
index 8fd83cfdd8f..6815c989546 100644
--- a/app/javascript/spec/evacuate-form/__snapshots__/evacuate-form.spec.js.snap
+++ b/app/javascript/spec/evacuate-form/__snapshots__/evacuate-form.spec.js.snap
@@ -133,6 +133,7 @@ exports[`evacuate form component should render evacuate form when hosts empty 1`
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -247,6 +248,7 @@ exports[`evacuate form component should render evacuate form when hosts empty 1`
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -2070,6 +2072,7 @@ exports[`evacuate form component should render evacuate form with host options 1
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -2205,6 +2208,7 @@ exports[`evacuate form component should render evacuate form with host options 1
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -4387,6 +4391,7 @@ exports[`evacuate form component should render evacuate form with multiple insta
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -4501,6 +4506,7 @@ exports[`evacuate form component should render evacuate form with multiple insta
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/generic-objects-form/__snapshots__/generic-objects-form.spec.js.snap b/app/javascript/spec/generic-objects-form/__snapshots__/generic-objects-form.spec.js.snap
index 3bfdf125352..f1dbb624af6 100644
--- a/app/javascript/spec/generic-objects-form/__snapshots__/generic-objects-form.spec.js.snap
+++ b/app/javascript/spec/generic-objects-form/__snapshots__/generic-objects-form.spec.js.snap
@@ -46,6 +46,7 @@ exports[`Generic Object Form Component should render adding a new generic object
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -307,6 +308,7 @@ exports[`Generic Object Form Component should render adding a new generic object
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -571,6 +573,7 @@ exports[`Generic Object Form Component should render adding a new generic object
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -4652,6 +4655,7 @@ exports[`Generic Object Form Component should render editing a generic object wi
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -4971,6 +4975,7 @@ exports[`Generic Object Form Component should render editing a generic object wi
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -5293,6 +5298,7 @@ exports[`Generic Object Form Component should render editing a generic object wi
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -12282,6 +12288,7 @@ exports[`Generic Object Form Component should render editing a generic object wi
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -12598,6 +12605,7 @@ exports[`Generic Object Form Component should render editing a generic object wi
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -12917,6 +12925,7 @@ exports[`Generic Object Form Component should render editing a generic object wi
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/host-aggregate-form/__snapshots__/host-aggregate-form.spec.js.snap b/app/javascript/spec/host-aggregate-form/__snapshots__/host-aggregate-form.spec.js.snap
index abe7e1d952e..ba3beb48f72 100644
--- a/app/javascript/spec/host-aggregate-form/__snapshots__/host-aggregate-form.spec.js.snap
+++ b/app/javascript/spec/host-aggregate-form/__snapshots__/host-aggregate-form.spec.js.snap
@@ -102,6 +102,7 @@ exports[`Host aggregate form component should render add host form variant (remv
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -163,6 +164,7 @@ exports[`Host aggregate form component should render add host form variant (remv
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/host-edit-form/__snapshots__/host-edit-form.spec.js.snap b/app/javascript/spec/host-edit-form/__snapshots__/host-edit-form.spec.js.snap
index 9e7faa30795..1057ebef1db 100644
--- a/app/javascript/spec/host-edit-form/__snapshots__/host-edit-form.spec.js.snap
+++ b/app/javascript/spec/host-edit-form/__snapshots__/host-edit-form.spec.js.snap
@@ -47,6 +47,7 @@ exports[`Show Edit Host Form Component should render form for *one* host 1`] = `
"protocol-selector": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -270,6 +271,7 @@ exports[`Show Edit Host Form Component should render form for *one* host 1`] = `
"protocol-selector": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -500,6 +502,7 @@ exports[`Show Edit Host Form Component should render form for *one* host 1`] = `
"protocol-selector": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -5541,6 +5544,7 @@ exports[`Show Edit Host Form Component should render form for multiple hosts 1`]
"protocol-selector": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -5603,6 +5607,7 @@ exports[`Show Edit Host Form Component should render form for multiple hosts 1`]
"protocol-selector": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -5672,6 +5677,7 @@ exports[`Show Edit Host Form Component should render form for multiple hosts 1`]
"protocol-selector": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/host-initiator-group-form/__snapshots__/host-initiator-group.spec.js.snap b/app/javascript/spec/host-initiator-group-form/__snapshots__/host-initiator-group.spec.js.snap
index 165568fa6d0..b8d86f17718 100644
--- a/app/javascript/spec/host-initiator-group-form/__snapshots__/host-initiator-group.spec.js.snap
+++ b/app/javascript/spec/host-initiator-group-form/__snapshots__/host-initiator-group.spec.js.snap
@@ -115,6 +115,7 @@ exports[`Host Initiator Group Form Loads data and renders 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -219,6 +220,7 @@ exports[`Host Initiator Group Form Loads data and renders 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/live-migrate-form/__snapshots__/live-migrate-form.spec.js.snap b/app/javascript/spec/live-migrate-form/__snapshots__/live-migrate-form.spec.js.snap
index 424de5b17c7..53837b0b900 100644
--- a/app/javascript/spec/live-migrate-form/__snapshots__/live-migrate-form.spec.js.snap
+++ b/app/javascript/spec/live-migrate-form/__snapshots__/live-migrate-form.spec.js.snap
@@ -125,6 +125,7 @@ exports[`Live Migrate form component should render live migrate form when hosts
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -237,6 +238,7 @@ exports[`Live Migrate form component should render live migrate form when hosts
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -2006,6 +2008,7 @@ exports[`Live Migrate form component should render live migrate form with host o
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -2139,6 +2142,7 @@ exports[`Live Migrate form component should render live migrate form with host o
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -4267,6 +4271,7 @@ exports[`Live Migrate form component should render live migrate form with multip
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -4379,6 +4384,7 @@ exports[`Live Migrate form component should render live migrate form with multip
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/physical-storage-form/__snapshots__/physical-storage-form.spec.js.snap b/app/javascript/spec/physical-storage-form/__snapshots__/physical-storage-form.spec.js.snap
index 183b6212b45..8bd08cbcbd5 100644
--- a/app/javascript/spec/physical-storage-form/__snapshots__/physical-storage-form.spec.js.snap
+++ b/app/javascript/spec/physical-storage-form/__snapshots__/physical-storage-form.spec.js.snap
@@ -36,6 +36,7 @@ exports[`Physical storage form component should render adding form variant 1`] =
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -312,6 +313,7 @@ exports[`Physical storage form component should render editing form variant 1`]
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -563,6 +565,7 @@ exports[`Physical storage form component should render editing form variant 1`]
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -817,6 +820,7 @@ exports[`Physical storage form component should render editing form variant 1`]
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/pxe-customization-template-form/__snapshots__/pxe-customization-template-form.spec.js.snap b/app/javascript/spec/pxe-customization-template-form/__snapshots__/pxe-customization-template-form.spec.js.snap
index 791bd8563af..f317184633c 100644
--- a/app/javascript/spec/pxe-customization-template-form/__snapshots__/pxe-customization-template-form.spec.js.snap
+++ b/app/javascript/spec/pxe-customization-template-form/__snapshots__/pxe-customization-template-form.spec.js.snap
@@ -145,6 +145,7 @@ exports[`Pxe Customization Template Form Component should render adding a new px
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -274,6 +275,7 @@ exports[`Pxe Customization Template Form Component should render adding a new px
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -2777,6 +2779,7 @@ exports[`Pxe Customization Template Form Component should render copying a pxe c
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -2915,6 +2918,7 @@ exports[`Pxe Customization Template Form Component should render copying a pxe c
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -5445,6 +5449,7 @@ exports[`Pxe Customization Template Form Component should render editing a pxe c
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -5583,6 +5588,7 @@ exports[`Pxe Customization Template Form Component should render editing a pxe c
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/pxe-image-type-form/__snapshots__/pxe-image-type-form.spec.js.snap b/app/javascript/spec/pxe-image-type-form/__snapshots__/pxe-image-type-form.spec.js.snap
index 9157994f317..fcb00cbfef0 100644
--- a/app/javascript/spec/pxe-image-type-form/__snapshots__/pxe-image-type-form.spec.js.snap
+++ b/app/javascript/spec/pxe-image-type-form/__snapshots__/pxe-image-type-form.spec.js.snap
@@ -100,6 +100,7 @@ exports[`Pxe Image Type Form Component should render adding a new pxe image type
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -190,6 +191,7 @@ exports[`Pxe Image Type Form Component should render adding a new pxe image type
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -1604,6 +1606,7 @@ exports[`Pxe Image Type Form Component should render editing a pxe image type 1`
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -1700,6 +1703,7 @@ exports[`Pxe Image Type Form Component should render editing a pxe image type 1`
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/pxe-iso-datastore-form/__snapshots__/pxe-iso-datastore-form.spec.js.snap b/app/javascript/spec/pxe-iso-datastore-form/__snapshots__/pxe-iso-datastore-form.spec.js.snap
index 885c991ac0a..9ec85abcba1 100644
--- a/app/javascript/spec/pxe-iso-datastore-form/__snapshots__/pxe-iso-datastore-form.spec.js.snap
+++ b/app/javascript/spec/pxe-iso-datastore-form/__snapshots__/pxe-iso-datastore-form.spec.js.snap
@@ -105,6 +105,7 @@ exports[`Pxe Iso Datastore Form Component should render adding a new iso datasto
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -188,6 +189,7 @@ exports[`Pxe Iso Datastore Form Component should render adding a new iso datasto
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/pxe-iso-image-form/__snapshots__/pxe-iso-image-form.spec.js.snap b/app/javascript/spec/pxe-iso-image-form/__snapshots__/pxe-iso-image-form.spec.js.snap
index 1de51401fe7..378f352cd21 100644
--- a/app/javascript/spec/pxe-iso-image-form/__snapshots__/pxe-iso-image-form.spec.js.snap
+++ b/app/javascript/spec/pxe-iso-image-form/__snapshots__/pxe-iso-image-form.spec.js.snap
@@ -86,6 +86,7 @@ exports[`Pxe Edit Iso Image Form Component should render editing a iso image 1`]
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -160,6 +161,7 @@ exports[`Pxe Edit Iso Image Form Component should render editing a iso image 1`]
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/reconfigure-vm-form/__snapshots__/reconfigure-vm-form.spec.js.snap b/app/javascript/spec/reconfigure-vm-form/__snapshots__/reconfigure-vm-form.spec.js.snap
index fb6094539ac..9204b9a5210 100644
--- a/app/javascript/spec/reconfigure-vm-form/__snapshots__/reconfigure-vm-form.spec.js.snap
+++ b/app/javascript/spec/reconfigure-vm-form/__snapshots__/reconfigure-vm-form.spec.js.snap
@@ -109,6 +109,7 @@ exports[`Reconfigure VM form component should render form with only fields it ha
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -418,6 +419,7 @@ exports[`Reconfigure VM form component should render form with only fields it ha
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -732,6 +734,7 @@ exports[`Reconfigure VM form component should render form with only fields it ha
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -4602,6 +4605,7 @@ exports[`Reconfigure VM form component should render reconfigure form and click
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -5015,6 +5019,7 @@ exports[`Reconfigure VM form component should render reconfigure form and click
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -5433,6 +5438,7 @@ exports[`Reconfigure VM form component should render reconfigure form and click
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -15957,6 +15963,7 @@ exports[`Reconfigure VM form component should render reconfigure form and show c
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -16072,6 +16079,7 @@ exports[`Reconfigure VM form component should render reconfigure form and show c
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -16192,6 +16200,7 @@ exports[`Reconfigure VM form component should render reconfigure form and show c
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -17435,6 +17444,7 @@ exports[`Reconfigure VM form component should render reconfigure form and show d
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -17655,6 +17665,7 @@ exports[`Reconfigure VM form component should render reconfigure form and show d
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -17880,6 +17891,7 @@ exports[`Reconfigure VM form component should render reconfigure form and show d
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -21239,6 +21251,7 @@ exports[`Reconfigure VM form component should render reconfigure form and show d
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -21459,6 +21472,7 @@ exports[`Reconfigure VM form component should render reconfigure form and show d
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -21684,6 +21698,7 @@ exports[`Reconfigure VM form component should render reconfigure form and show d
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -25022,6 +25037,7 @@ exports[`Reconfigure VM form component should render reconfigure form and show h
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -25248,6 +25264,7 @@ exports[`Reconfigure VM form component should render reconfigure form and show h
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -25479,6 +25496,7 @@ exports[`Reconfigure VM form component should render reconfigure form and show h
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -30082,6 +30100,7 @@ exports[`Reconfigure VM form component should render reconfigure form and show n
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -30227,6 +30246,7 @@ exports[`Reconfigure VM form component should render reconfigure form and show n
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -30377,6 +30397,7 @@ exports[`Reconfigure VM form component should render reconfigure form and show n
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -32194,6 +32215,7 @@ exports[`Reconfigure VM form component should render reconfigure form with datat
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -32608,6 +32630,7 @@ exports[`Reconfigure VM form component should render reconfigure form with datat
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -33027,6 +33050,7 @@ exports[`Reconfigure VM form component should render reconfigure form with datat
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -43724,6 +43748,7 @@ exports[`Reconfigure VM form component should render reconfigure form without da
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -43950,6 +43975,7 @@ exports[`Reconfigure VM form component should render reconfigure form without da
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -44181,6 +44207,7 @@ exports[`Reconfigure VM form component should render reconfigure form without da
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -48784,6 +48811,7 @@ exports[`Reconfigure VM form component should render reconfigure sub form and cl
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -49196,6 +49224,7 @@ exports[`Reconfigure VM form component should render reconfigure sub form and cl
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -49613,6 +49642,7 @@ exports[`Reconfigure VM form component should render reconfigure sub form and cl
"radio": [Function],
"reconfigure-table": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/schedule-form/__snapshots__/schedule-form.spec.js.snap b/app/javascript/spec/schedule-form/__snapshots__/schedule-form.spec.js.snap
index 4b0ba541a76..84a38796e66 100644
--- a/app/javascript/spec/schedule-form/__snapshots__/schedule-form.spec.js.snap
+++ b/app/javascript/spec/schedule-form/__snapshots__/schedule-form.spec.js.snap
@@ -737,6 +737,7 @@ exports[`Schedule form component should render edit form when filter_type is not
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -1380,6 +1381,7 @@ exports[`Schedule form component should render edit form when filter_type is not
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -15556,6 +15558,7 @@ exports[`Schedule form component should render edit form when filter_type is nul
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -16341,6 +16344,7 @@ exports[`Schedule form component should render edit form when filter_type is nul
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -33750,6 +33754,7 @@ exports[`Schedule form component should render schedule add form 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -34316,6 +34321,7 @@ exports[`Schedule form component should render schedule add form 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/service-request-default-form/__snapshots__/service-request-default-form.spec.js.snap b/app/javascript/spec/service-request-default-form/__snapshots__/service-request-default-form.spec.js.snap
index 9974503a718..3a2ee28417a 100644
--- a/app/javascript/spec/service-request-default-form/__snapshots__/service-request-default-form.spec.js.snap
+++ b/app/javascript/spec/service-request-default-form/__snapshots__/service-request-default-form.spec.js.snap
@@ -336,6 +336,7 @@ exports[`Show Service Request Page should render 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -540,6 +541,7 @@ exports[`Show Service Request Page should render 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/settings-category-form/__snapshots__/settings-category-form.spec.js.snap b/app/javascript/spec/settings-category-form/__snapshots__/settings-category-form.spec.js.snap
index 911a57d8572..ba891ac282f 100644
--- a/app/javascript/spec/settings-category-form/__snapshots__/settings-category-form.spec.js.snap
+++ b/app/javascript/spec/settings-category-form/__snapshots__/settings-category-form.spec.js.snap
@@ -147,6 +147,7 @@ exports[`SettingsCategoryForm Component should render a new SettingsCategoryForm
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -282,6 +283,7 @@ exports[`SettingsCategoryForm Component should render a new SettingsCategoryForm
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/settings-time-profile-form/__snapshots__/settings-time-profile-form.spec.js.snap b/app/javascript/spec/settings-time-profile-form/__snapshots__/settings-time-profile-form.spec.js.snap
index f55f28a80b2..fbce04573fa 100644
--- a/app/javascript/spec/settings-time-profile-form/__snapshots__/settings-time-profile-form.spec.js.snap
+++ b/app/javascript/spec/settings-time-profile-form/__snapshots__/settings-time-profile-form.spec.js.snap
@@ -507,6 +507,7 @@ exports[`VM common form component should render adding form variant add new time
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -1008,6 +1009,7 @@ exports[`VM common form component should render adding form variant add new time
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/user-form/__snapshots__/user-form.spec.js.snap b/app/javascript/spec/user-form/__snapshots__/user-form.spec.js.snap
new file mode 100644
index 00000000000..132cf306658
--- /dev/null
+++ b/app/javascript/spec/user-form/__snapshots__/user-form.spec.js.snap
@@ -0,0 +1,41 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`User Form Component should render add User form correctly 1`] = `
+
+`;
+
+exports[`User Form Component should render copy User form correclty 1`] = `
+
+`;
+
+exports[`User Form Component should render edit Admin User form correclty 1`] = `
+
+`;
+
+exports[`User Form Component should render edit User form correclty 1`] = `
+
+`;
diff --git a/app/javascript/spec/user-form/user-form.spec.js b/app/javascript/spec/user-form/user-form.spec.js
new file mode 100644
index 00000000000..6cf54a6c61c
--- /dev/null
+++ b/app/javascript/spec/user-form/user-form.spec.js
@@ -0,0 +1,99 @@
+import React from 'react';
+import fetchMock from 'fetch-mock';
+import { shallow } from 'enzyme';
+import toJson from 'enzyme-to-json';
+import UserForm from '../../components/user-form/index';
+
+describe('User Form Component', () => {
+ const groupsMockData = [
+ {
+ href: 'http://localhost:3000/api/groups/2',
+ id: '2',
+ description: 'EvmGroup-super_adminstrator',
+ },
+ {
+ href: 'http://localhost:3000/api/groups/3',
+ id: '3',
+ description: 'EvmGroup-operator',
+ },
+ {
+ href: 'http://localhost:3000/api/groups/4',
+ id: '4',
+ description: 'EvmGroup-user',
+ },
+ ];
+
+ const userMockData = {
+ current_group_id: '40',
+ email: 'test@email.com',
+ id: '41',
+ name: 'test name',
+ userid: 'testuser',
+ };
+
+ const adminMockData = {
+ current_group_id: '40',
+ email: 'test@email.com',
+ id: '1',
+ name: 'Administrator',
+ userid: 'admin',
+ };
+
+ const userData = {
+ current_group_id: '2',
+ email: 'test@email.com',
+ name: 'test name',
+ userid: 'testuser',
+ };
+
+ afterEach(() => {
+ fetchMock.reset();
+ fetchMock.restore();
+ });
+
+ it('should render add User form correctly', async(done) => {
+ const wrapper = shallow();
+ fetchMock.get('/api/groups?&expand=resources', groupsMockData);
+
+ setImmediate(() => {
+ wrapper.update();
+ expect(toJson(wrapper)).toMatchSnapshot();
+ done();
+ });
+ });
+
+ it('should render edit User form correclty', async(done) => {
+ const wrapper = shallow();
+ fetchMock.get('/api/groups?&expand=resources', groupsMockData);
+ fetchMock.get('/api/users/41', userMockData);
+
+ setImmediate(() => {
+ wrapper.update();
+ expect(toJson(wrapper)).toMatchSnapshot();
+ done();
+ });
+ });
+
+ it('should render edit Admin User form correclty', async(done) => {
+ const wrapper = shallow();
+ fetchMock.get('/api/groups?&expand=resources', groupsMockData);
+ fetchMock.get('/api/users/1', adminMockData);
+
+ setImmediate(() => {
+ wrapper.update();
+ expect(toJson(wrapper)).toMatchSnapshot();
+ done();
+ });
+ });
+
+ it('should render copy User form correclty', async(done) => {
+ const wrapper = shallow();
+ fetchMock.get('/api/groups?&expand=resources', groupsMockData);
+
+ setImmediate(() => {
+ wrapper.update();
+ expect(toJson(wrapper)).toMatchSnapshot();
+ done();
+ });
+ });
+});
diff --git a/app/javascript/spec/vm-floating-ips-form/__snapshots__/vm-floating-ips-form.spec.js.snap b/app/javascript/spec/vm-floating-ips-form/__snapshots__/vm-floating-ips-form.spec.js.snap
index e0249f182c7..83ce08873d7 100644
--- a/app/javascript/spec/vm-floating-ips-form/__snapshots__/vm-floating-ips-form.spec.js.snap
+++ b/app/javascript/spec/vm-floating-ips-form/__snapshots__/vm-floating-ips-form.spec.js.snap
@@ -69,6 +69,7 @@ exports[`Associate / Disassociate form component should render associate form va
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -135,6 +136,7 @@ exports[`Associate / Disassociate form component should render associate form va
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -1101,6 +1103,7 @@ exports[`Associate / Disassociate form component should render disassociate form
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -1167,6 +1170,7 @@ exports[`Associate / Disassociate form component should render disassociate form
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -2160,6 +2164,7 @@ exports[`Associate / Disassociate form component should submit Associate API cal
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -2226,6 +2231,7 @@ exports[`Associate / Disassociate form component should submit Associate API cal
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -3072,6 +3078,7 @@ exports[`Associate / Disassociate form component should submit Disassociate API
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -3138,6 +3145,7 @@ exports[`Associate / Disassociate form component should submit Disassociate API
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/vm-resize-form/__snapshots__/vm-resize-form.spec.js.snap b/app/javascript/spec/vm-resize-form/__snapshots__/vm-resize-form.spec.js.snap
index 15a030ad2b2..470650cc69d 100644
--- a/app/javascript/spec/vm-resize-form/__snapshots__/vm-resize-form.spec.js.snap
+++ b/app/javascript/spec/vm-resize-form/__snapshots__/vm-resize-form.spec.js.snap
@@ -79,6 +79,7 @@ exports[`vm resize form component should render a resize form 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -146,6 +147,7 @@ exports[`vm resize form component should render a resize form 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/workflow-credential-mapping-form/__snapshots__/workflow-credential-mapping-form.spec.js.snap b/app/javascript/spec/workflow-credential-mapping-form/__snapshots__/workflow-credential-mapping-form.spec.js.snap
index 46dbe52751e..3d9dd8266e3 100644
--- a/app/javascript/spec/workflow-credential-mapping-form/__snapshots__/workflow-credential-mapping-form.spec.js.snap
+++ b/app/javascript/spec/workflow-credential-mapping-form/__snapshots__/workflow-credential-mapping-form.spec.js.snap
@@ -72,6 +72,7 @@ exports[`Workflow Credential Form Component should render mapping credentials to
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -261,6 +262,7 @@ exports[`Workflow Credential Form Component should render mapping credentials to
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -453,6 +455,7 @@ exports[`Workflow Credential Form Component should render mapping credentials to
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/workflow-credentials-form/__snapshots__/workflow-credentials-form.spec.js.snap b/app/javascript/spec/workflow-credentials-form/__snapshots__/workflow-credentials-form.spec.js.snap
index 3c8754b5f07..fa355b7ffbf 100644
--- a/app/javascript/spec/workflow-credentials-form/__snapshots__/workflow-credentials-form.spec.js.snap
+++ b/app/javascript/spec/workflow-credentials-form/__snapshots__/workflow-credentials-form.spec.js.snap
@@ -107,6 +107,7 @@ exports[`Workflow Credential Form Component should render adding a new credentia
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -204,6 +205,7 @@ exports[`Workflow Credential Form Component should render adding a new credentia
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
@@ -1618,6 +1620,7 @@ exports[`Workflow Credential Form Component should render editing a credential 1
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -1722,6 +1725,7 @@ exports[`Workflow Credential Form Component should render editing a credential 1
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/javascript/spec/zone-form/__snapshots__/zone-form.spec.js.snap b/app/javascript/spec/zone-form/__snapshots__/zone-form.spec.js.snap
index 7f7416f31a0..8302a222d06 100644
--- a/app/javascript/spec/zone-form/__snapshots__/zone-form.spec.js.snap
+++ b/app/javascript/spec/zone-form/__snapshots__/zone-form.spec.js.snap
@@ -321,6 +321,7 @@ exports[`zone Form Component should render editing a zone form 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"sub-form": [Function],
"switch": [Function],
@@ -494,6 +495,7 @@ exports[`zone Form Component should render editing a zone form 1`] = `
"plain-text": [Function],
"radio": [Function],
"select": [Function],
+ "selected-groups-list": [Function],
"slider": [Function],
"spy-field": [Function],
"sub-form": [Function],
diff --git a/app/stylesheet/application-webpack.scss b/app/stylesheet/application-webpack.scss
index 51aaf564eb8..9c5a81f6fa0 100644
--- a/app/stylesheet/application-webpack.scss
+++ b/app/stylesheet/application-webpack.scss
@@ -32,3 +32,4 @@
@import './widget.scss';
@import './workflows.scss';
@import './cloud-container-projects-dashboard.scss';
+@import './selected-groups-list.scss';
diff --git a/app/stylesheet/ddf_override.scss b/app/stylesheet/ddf_override.scss
index fa6e599fbe5..e2ca8632184 100644
--- a/app/stylesheet/ddf_override.scss
+++ b/app/stylesheet/ddf_override.scss
@@ -526,3 +526,18 @@
margin-right: 5%;
}
}
+
+.user-form {
+ #passwordWarning {
+ color: #da1e28;
+ font-size: 14px;
+ }
+
+ .custom-button-wrapper {
+ margin-top: 10px;
+
+ .submit-button {
+ margin-right: 8px;
+ }
+ }
+}
diff --git a/app/stylesheet/selected-groups-list.scss b/app/stylesheet/selected-groups-list.scss
new file mode 100644
index 00000000000..62d01fea524
--- /dev/null
+++ b/app/stylesheet/selected-groups-list.scss
@@ -0,0 +1,24 @@
+#selected-groups-label {
+ display: inline-flex;
+ font-family: inherit;
+ font-size: 0.75rem;
+ letter-spacing: 0.32px;
+ color: #525252;
+ font-weight: 400;
+ line-height: 1rem;
+}
+
+#selected-groups {
+ padding-left: 3rem;
+ display: inline-table;
+}
+
+.group-name {
+ display: inline-flex;
+ font-family: "Open Sans", Helvetica, Arial, sans-serif;
+ font-size: 12px;
+ line-height: 1.67;
+ color: #363636;
+ padding-left: 2px;
+ margin-bottom: 0px;
+}
diff --git a/app/views/ops/_rbac_user_details.html.haml b/app/views/ops/_rbac_user_details.html.haml
index e65c4aa23b3..5cf877cdf5a 100644
--- a/app/views/ops/_rbac_user_details.html.haml
+++ b/app/views/ops/_rbac_user_details.html.haml
@@ -1,114 +1,11 @@
- if @edit
- - change_stored_password ||= _("Change")
- - cancel_password_change ||= _("Cancel")
- - stored_password_placeholder = "●●●●●●●●"
- - pfx ||= ""
- - url = url_for_only_path(:action => 'rbac_user_field_changed', :id => (@edit[:user_id] || "new"))
- - observe_url_json = {:interval => '.5', :url => url}.to_json
- - disabled = @edit && @edit[:new][:userid] == "admin"
= render :partial => "layouts/flash_msg"
+ - disabled = @edit && @edit[:new][:userid] == "admin"
+ - db_mode = ::Settings.authentication.mode
#user_info
%h3
= _("User Information")
- .form-horizontal
- .form-group
- %label.col-md-2.control-label
- = _("Full Name")
- .col-md-8
- = text_field_tag("name",
- @edit[:new][:name],
- :autocomplete => 'off',
- :maxlength => 50,
- :disabled => disabled,
- :class => "form-control",
- "data-miq_observe" => observe_url_json)
- - unless @edit[:new][:userid] == "admin"
- = javascript_tag(javascript_focus('name'))
- .form-group
- %label.col-md-2.control-label
- = _("Username")
- .col-md-8
- = text_field_tag("userid",
- @edit[:new][:userid],
- :autocomplete => 'off',
- :maxlength => 255,
- :disabled => disabled,
- :class => "form-control",
- "data-miq_observe" => observe_url_json)
- - db_mode = ::Settings.authentication.mode
- - if db_mode == "database" || (db_mode != "database" && disabled)
- .form-group
- %label.col-md-2.control-label
- = _("Password")
- .col-md-8
- %span.input-group{:style => "width: 100%"}
- = password_field_tag("password",
- "",
- :autocomplete => "new-password",
- :maxlength => 50,
- :disabled => @edit[:new][:userid].blank? ? false : true,
- :placeholder => @edit[:new][:userid].blank? ? "" : stored_password_placeholder,
- :class => "form-control",
- "data-miq_observe" => observe_url_json)
- - if @edit[:new][:userid] == "admin"
- = javascript_tag(javascript_focus('password'))
- - unless @edit[:new][:userid].blank?
- %span.input-group-btn
- %button.btn.btn-default{:id => "change_stored_password",
- "style" => "display:block;cursor: pointer; cursor: hand;", "onclick" => "changeStoredPassword('#{pfx}', '#{url}')"}
- = change_stored_password
- %button.btn.btn-default{:id => "cancel_password_change",
- "style" => "display:none;cursor: pointer; cursor: hand;",
- "onclick" => "cancelPasswordChange('#{pfx}', '#{url}')"}
- = cancel_password_change
- .form-group{:id => "verify_group",
- :style => @edit[:new][:userid].blank? ? "display:block" : "display:none"}
- %label.col-md-2.control-label
- = _("Confirm Password")
- .col-md-8
- = password_field_tag("verify",
- "",
- :autocomplete => "new-password",
- :maxlength => 50,
- :class => "form-control",
- "data-miq_observe" => observe_url_json)
- .form-group
- %label.col-md-2.control-label
- = _("E-mail Address")
- .col-md-8
- = text_field_tag("email",
- @edit[:new][:email],
- :autocomplete => 'off',
- :maxlength => 253,
- :class => "form-control",
- "data-miq_observe" => observe_url_json)
- .form-group
- %label.col-md-2.control-label
- = _("Available Groups")
- .col-md-2
- - groups = @record.present? && @record.miq_groups.present? ? @record.miq_groups.order(:description) : []
- - if disabled
- %p.form-control-static
- - groups.each do |group|
- = h(group.description)
- %br
- - else
- - select_groups = @edit[:new][:userid].blank? ? @edit[:new][:group] : groups.map(&:id)
- = select_tag('chosen_group',
- options_for_select(@edit[:groups], select_groups),
- :class => "selectpicker",
- :multiple => true,
- :title => "<#{_('Choose one or more Groups')}>",
- :style => "overflow-x: scroll;")
- :javascript
- miqInitSelectPicker();
- miqSelectPickerEvent('chosen_group', "#{url}")
- .form-group
- %label.col-md-2.control-label
- = _("Selected Groups")
- .col-md-4
- = render :partial => "ops/rbac_group_selected"
- %hr
+ = react('UserForm', {:id => (@edit[:user_id]), :copyUserId => @copy_user_id, :disabled => disabled, :dbMode => db_mode})
- else
= settings_users_summary(@record)
= render :partial => "rbac_tag_box"
diff --git a/config/routes.rb b/config/routes.rb
index 6be2977c352..ba55c82139e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -2472,7 +2472,6 @@
rbac_tenants_list
rbac_tenant_manage_quotas
rbac_user_edit
- rbac_user_field_changed
rbac_users_list
region_edit
restart_server
diff --git a/cypress/e2e/ui/Settings/application-settings.cy.js b/cypress/e2e/ui/Settings/application-settings.cy.js
new file mode 100644
index 00000000000..22fba8dcc23
--- /dev/null
+++ b/cypress/e2e/ui/Settings/application-settings.cy.js
@@ -0,0 +1,744 @@
+/* eslint-disable no-undef */
+
+describe('Settings > Application Settings Tests', () => {
+ beforeEach(() => {
+ cy.login();
+ cy.intercept('POST', '/ops/accordion_select?id=rbac_accord').as('accordion');
+ cy.menu('Settings', 'Application Settings');
+ cy.get('#settings_server > :nth-child(5)'); // Waits for form to load or else explorer breaks
+ });
+
+ describe('Access Control', () => {
+ beforeEach(() => {
+ cy.get('#control_rbac_accord > .panel-title > .collapsed').click();
+ cy.wait('@accordion'); // Wait for explorer screen to load
+ });
+
+ describe('Users', () => {
+ it('Correctly loads admin user', () => {
+ // Navigate to user list and click on Administrator user in table
+ cy.get('[title="View Users"] > .content_value').click({force: true});
+ cy.get('.clickable-row').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ if (rows[index].children[1].textContent === 'Administrator') {
+ cy.get(rows[index].children[1]).click({ force: true });
+ }
+ });
+ });
+
+ // Check Administrator user values on summary page
+ cy.get('#accordion-item-15 > .bx--structured-list > .bx--structured-list-tbody > :nth-child(1) > .label_header').contains('ID');
+ cy.get(':nth-child(1) > .content_value > .content').contains('1');
+ cy.get(':nth-child(2) > .label_header').contains('Full Name');
+ cy.get(':nth-child(2) > .content_value').contains('Administrator');
+ cy.get(':nth-child(3) > .label_header').contains('Username');
+ cy.get(':nth-child(3) > .content_value').contains('admin');
+ cy.get(':nth-child(4) > .label_header').contains('E-mail Address');
+ cy.get(':nth-child(4) > .content_value').then((val) => {
+ expect(val[0].innerText).to.eq('');
+ });
+ cy.get(':nth-child(5) > .label_header').contains('Current Group');
+ cy.get(':nth-child(5) > .content_value').contains('EvmGroup-super_administrator');
+ cy.get(':nth-child(6) > .label_header').contains('Role');
+ cy.get(':nth-child(6) > .content_value').contains('EvmRole-super_administrator');
+ cy.get('.label_header > .bx--link > .content > .expand').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ expect(rows[index].innerText).to.eq('EvmGroup-super_administrator');
+ });
+ });
+
+ // Click edit button
+ cy.get('#user_vmdb_choice').click();
+ cy.get(':nth-child(1) > .bx--overflow-menu-options__btn > div').click();
+
+ // Check that fields are correctly disabled and contain the correct values
+ cy.get('#name').then((val) => {
+ expect(val[0].disabled).to.eq(true);
+ expect(val[0].defaultValue).to.eq('Administrator');
+ });
+ cy.get('#userid').then((val) => {
+ expect(val[0].disabled).to.eq(true);
+ expect(val[0].defaultValue).to.eq('admin');
+ });
+ cy.get('#passwordPlaceholder').then((val) => {
+ expect(val[0].disabled).to.eq(true);
+ });
+ cy.get('#email').then((val) => {
+ expect(val[0].disabled).to.eq(false);
+ expect(val[0].defaultValue).to.eq('');
+ });
+ cy.get('#downshift-0-toggle-button').then((val) => {
+ expect(val[0].disabled).to.eq(true);
+ });
+ cy.get('#EvmGroup-super_administrator').contains('EvmGroup-super_administrator');
+ });
+
+ it('Edit admin user', () => {
+ // Navigate to user list and click on Administrator user in table
+ cy.get('[title="View Users"] > .content_value').click({force: true});
+ cy.get('.clickable-row').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ if (rows[index].children[1].textContent === 'Administrator') {
+ cy.get(rows[index].children[1]).click({ force: true });
+ }
+ });
+ });
+
+ // Click edit button
+ cy.get('#user_vmdb_choice').click();
+ cy.get(':nth-child(1) > .bx--overflow-menu-options__btn > div').click();
+
+ // Edit the email field since that is the only field that the Adminisistrator user can change not including the password field
+ cy.get('#email').type('test@email.com');
+ cy.get('.bx--btn-set > .bx--btn--primary').click();
+ cy.get(':nth-child(4) > .label_header').contains('E-mail Address');
+ cy.get(':nth-child(4) > .content_value').contains('test@email.com'); // Check that email was correctly saved on the summary page
+
+ // Click edit button
+ cy.get('#user_vmdb_choice').click();
+ cy.get(':nth-child(1) > .bx--overflow-menu-options__btn > div').click();
+
+ // Reset email back to default value of empty
+ cy.get('#email').clear();
+ cy.get('.bx--btn-set > .bx--btn--primary').click();
+ });
+
+ it('Create, edit and delete a user', () => {
+ let groups = [];
+
+ // Navigate to user list and click Add a new User button
+ cy.get('[title="View Users"] > .content_value').click({force: true});
+ cy.get('#user_vmdb_choice').click();
+ cy.get(':nth-child(1) > .bx--overflow-menu-options__btn').click();
+
+ // Input values on the user form
+ cy.get('#name').type('Cypress Test Add', {force: true});
+ cy.get('#userid').type('cypressUserAdd', {force: true});
+ cy.get('#password').type('cypressPass');
+ cy.get('#confirmPassword').type('cypressPass');
+ cy.get('#email').type('test@email.com');
+ cy.get('#downshift-0-toggle-button').click();
+ cy.get('#downshift-0-menu').then((val) => {
+ // Select the first group
+ cy.get(val[0].children[0]).click();
+ groups.push(val[0].children[0].innerText);
+ });
+ cy.get('#downshift-0-toggle-button').click();
+ cy.get('#selected-groups > div > p').then((selectedGroups) => {
+ const nums = [...Array(selectedGroups.length).keys()];
+ nums.forEach((index) => {
+ expect(groups.includes(selectedGroups[index].textContent)).to.eq(true); // Check that multi select and selected groups list component work together correctly
+ });
+ });
+ cy.get('.bx--btn-set > .bx--btn--primary').click(); // Click the add button
+
+ // Find the new user in the table and click it
+ cy.get('.clickable-row').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ if (rows[index].children[1].textContent === 'Cypress Test Add') {
+ cy.get(rows[index].children[1]).click({ force: true });
+ }
+ });
+ });
+
+ // Verify that the new user was created with the correct values on the summary page
+ cy.get(':nth-child(1) > .label_header').contains('ID');
+ cy.get(':nth-child(2) > .label_header').contains('Full Name');
+ cy.get(':nth-child(2) > .content_value').contains('Cypress Test Add');
+ cy.get(':nth-child(3) > .label_header').contains('Username');
+ cy.get(':nth-child(3) > .content_value').contains('cypressUserAdd');
+ cy.get(':nth-child(4) > .label_header').contains('E-mail Address');
+ cy.get(':nth-child(4) > .content_value').contains('test@email.com');
+ cy.get(':nth-child(5) > .label_header').contains('Current Group');
+ cy.get(':nth-child(5) > .content_value').contains('EvmGroup-administrator');
+ cy.get(':nth-child(6) > .label_header').contains('Role');
+ cy.get(':nth-child(6) > .content_value').contains('EvmRole-administrator');
+ cy.get('.label_header > .bx--link > .content > .expand').then((rows) => { // Check groups list to verify user was correctly added to all selected groups
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ expect(groups.includes(rows[index].innerText)).to.eq(true);
+ });
+ });
+
+ // Logout of admin user and login to the new user account and logout again
+ cy.get('#menu_item_logout').click();
+ cy.get('#user_name').type('cypressUserAdd');
+ cy.get('#user_password').type('cypressPass');
+ cy.get('#login').click();
+ cy.get('#menu_item_logout').click();
+
+ // Login to admin user again and navigate to user table
+ cy.login();
+ cy.intercept('POST', '/ops/accordion_select?id=rbac_accord').as('accordion');
+ cy.menu('Settings', 'Application Settings');
+ cy.get('#settings_server > :nth-child(5)');
+ cy.get('#control_rbac_accord > .panel-title > .collapsed').click();
+ cy.wait('@accordion'); // Wait for explorer screen to load
+ cy.get('[title="View Users"] > .content_value').click({force: true});
+
+ // Find the new user in the table and click on that row
+ cy.get('.clickable-row').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ if (rows[index].children[1].textContent === 'Cypress Test Add') {
+ cy.get(rows[index].children[1]).click({ force: true });
+ }
+ });
+ });
+
+ // Click the edit user button
+ cy.get('#user_vmdb_choice').click();
+ cy.get(':nth-child(1) > .bx--overflow-menu-options__btn').click();
+
+ // Edit the values on the user form
+ cy.get('#name').clear({force: true}).type('Cypress Test Edit', {force: true});
+ cy.get('#userid').clear({force: true}).type('cypressUserEdit', {force: true});
+ cy.get('.bx--col-sm-1 > .bx--btn').click();
+ cy.get('#password').type('newPassword');
+ cy.get('#confirmPassword').type('newPassword');
+ cy.get('#email').clear().type('test_edit@email.com');
+ cy.get('#downshift-0-toggle-button').click({force: true});
+ groups = [];
+ cy.get('#downshift-0-menu').then((val) => {
+ // Unselect first group and select next two groups
+ cy.get(val[0].children[0]).click();
+ cy.get(val[0].children[1]).click();
+ cy.get(val[0].children[2]).click();
+
+ groups.push(val[0].children[1].innerText);
+ groups.push(val[0].children[2].innerText);
+ });
+ cy.get('#downshift-0-toggle-button').click({force: true});
+ cy.get('#selected-groups > div > p').then((selectedGroups) => {
+ const nums = [...Array(selectedGroups.length).keys()];
+ nums.forEach((index) => {
+ expect(groups.includes(selectedGroups[index].textContent)).to.eq(true);
+ });
+ });
+ cy.get('.bx--btn-set > .bx--btn--primary').click(); // Click save button
+
+ // Verify that the new user was edited with the correct values on the summary page
+ cy.get(':nth-child(1) > .label_header').contains('ID');
+ cy.get(':nth-child(2) > .label_header').contains('Full Name');
+ cy.get(':nth-child(2) > .content_value').contains('Cypress Test Edit');
+ cy.get(':nth-child(3) > .label_header').contains('Username');
+ cy.get(':nth-child(3) > .content_value').contains('cypressUserEdit');
+ cy.get(':nth-child(4) > .label_header').contains('E-mail Address');
+ cy.get(':nth-child(4) > .content_value').contains('test_edit@email.com');
+ cy.get(':nth-child(5) > .label_header').contains('Current Group');
+ cy.get(':nth-child(5) > .content_value').contains('EvmGroup-approver');
+ cy.get(':nth-child(6) > .label_header').contains('Role');
+ cy.get(':nth-child(6) > .content_value').contains('EvmRole-approver');
+ cy.get('.label_header > .bx--link > .content > .expand').then((rows) => { // Check groups list to verify user was correctly added to all selected groups
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ expect(groups.includes(rows[index].innerText)).to.eq(true);
+ });
+ });
+
+ // Logout of admin user and login to the edited account and logout again
+ cy.get('#menu_item_logout').click();
+ cy.get('#user_name').type('cypressUserEdit');
+ cy.get('#user_password').type('newPassword');
+ cy.get('#login').click();
+ cy.get('#menu_item_logout').click();
+
+ // Login to admin user again and navigate to user table
+ cy.login();
+ cy.intercept('POST', '/ops/accordion_select?id=rbac_accord').as('accordion');
+ cy.menu('Settings', 'Application Settings');
+ cy.get('#settings_server > :nth-child(5)');
+ cy.get('#control_rbac_accord > .panel-title > .collapsed').click();
+ cy.wait('@accordion'); // Wait for explorer screen to load
+ cy.get('[title="View Users"] > .content_value').click({force: true});
+
+ // Find the editted user in the table and click it
+ cy.get('.clickable-row').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ if (rows[index].children[1].textContent === 'Cypress Test Edit') {
+ cy.get(rows[index].children[1]).click({ force: true });
+ }
+ });
+ });
+
+ // Click the delete user button
+ cy.get('#user_vmdb_choice').click();
+ cy.get(':nth-child(3) > .bx--overflow-menu-options__btn').click();
+
+ // Verify that the user was deleted from the table
+ cy.get('.clickable-row').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ expect(rows[index].children[1].textContent).to.not.eq('Cypress Test Add');
+ expect(rows[index].children[1].textContent).to.not.eq('Cypress Test Edit');
+ });
+ });
+ });
+
+ it('Create, copy and delete a user', () => {
+ let groups = [];
+
+ // Navigate to user list and click Add a new User button
+ cy.get('[title="View Users"] > .content_value').click({force: true});
+ cy.get('#user_vmdb_choice').click();
+ cy.get(':nth-child(1) > .bx--overflow-menu-options__btn').click();
+
+ // Input values on the user form
+ cy.get('#name').type('Cypress Test Add 2', {force: true});
+ cy.get('#userid').type('cypressUserAdd2', {force: true});
+ cy.get('#password').type('cypressPass');
+ cy.get('#confirmPassword').type('cypressPass');
+ cy.get('#email').type('test@email.com');
+ cy.get('#downshift-0-toggle-button').click();
+ cy.get('#downshift-0-menu').then((val) => {
+ // Click first two
+ cy.get(val[0].children[0]).click();
+ cy.get(val[0].children[1]).click();
+
+ groups.push(val[0].children[0].innerText);
+ groups.push(val[0].children[1].innerText);
+ });
+ cy.get('#downshift-0-toggle-button').click();
+ cy.get('#selected-groups > div > p').then((selectedGroups) => {
+ const nums = [...Array(selectedGroups.length).keys()];
+ nums.forEach((index) => {
+ expect(groups.includes(selectedGroups[index].textContent)).to.eq(true); // Check that multi select and selected groups list component work together correctly
+ });
+ });
+ cy.get('.bx--btn-set > .bx--btn--primary').click(); // Click the add button
+
+ // Find the new user in the table and click it
+ cy.get('.clickable-row').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ if (rows[index].children[1].textContent === 'Cypress Test Add 2') {
+ cy.get(rows[index].children[1]).click({ force: true });
+ }
+ });
+ });
+
+ // Click copy user button
+ cy.get('#user_vmdb_choice').click();
+ cy.get(':nth-child(2) > .bx--overflow-menu-options__btn').click();
+
+ // Verify copy form was loaded with the correct values
+ cy.get('#name').then((val) => {
+ expect(val[0].defaultValue).to.eq('Cypress Test Add 2');
+ });
+ cy.get('#userid').then((val) => {
+ expect(val[0].defaultValue).to.eq('');
+ });
+ cy.get('#password').then((val) => {
+ expect(val[0].defaultValue).to.eq('');
+ });
+ cy.get('#confirmPassword').then((val) => {
+ expect(val[0].defaultValue).to.eq('');
+ });
+ cy.get('#email').then((val) => {
+ expect(val[0].defaultValue).to.eq('test@email.com');
+ });
+
+ // Check the multi-select and selected groups list initial selected values
+ cy.get('#downshift-0-toggle-button').click();
+ cy.get('#downshift-0-menu').then((val) => {
+ val[0].children.forEach((group) => {
+ if (groups.includes(group.textContent)) {
+ expect(group.children[0].children[0].children[0].getAttribute('data-contained-checkbox-state')).to.eq('true');
+ } else {
+ expect(group.children[0].children[0].children[0].getAttribute('data-contained-checkbox-state')).to.eq('false');
+ }
+ });
+ });
+ cy.get('#downshift-0-toggle-button').click();
+ cy.get('#selected-groups > div > p').then((selectedGroups) => {
+ const nums = [...Array(selectedGroups.length).keys()];
+ nums.forEach((index) => {
+ expect(groups.includes(selectedGroups[index].textContent)).to.eq(true);
+ });
+ });
+
+ // Input the new values on the copy user form
+ cy.get('#name').clear().type('Cypress Test Copy');
+ cy.get('#userid').type('cypressUserCopy');
+ cy.get('#password').type('newPassword');
+ cy.get('#confirmPassword').type('newPassword');
+ cy.get('#email').clear().type('test_copy@email.com');
+ cy.get('#downshift-0-toggle-button').click({force: true});
+ groups = [];
+ cy.get('#downshift-0-menu').then((val) => {
+ // Unselect first group and leave second group selected
+ cy.get(val[0].children[0]).click();
+
+ groups.push(val[0].children[1].innerText);
+ });
+ cy.get('#downshift-0-toggle-button').click({force: true});
+ cy.get('#selected-groups > div > p').then((selectedGroups) => {
+ const nums = [...Array(selectedGroups.length).keys()];
+ nums.forEach((index) => {
+ expect(groups.includes(selectedGroups[index].textContent)).to.eq(true);
+ });
+ });
+ cy.get('.bx--btn-set > .bx--btn--primary').click(); // Click the add button
+
+ // Wait for summary page to load
+ cy.get(':nth-child(1) > .label_header').contains('ID');
+
+ // Logout of admin user and login to the new user account and logout again
+ cy.get('#menu_item_logout').click();
+ cy.get('#user_name').type('cypressUserAdd2');
+ cy.get('#user_password').type('cypressPass');
+ cy.get('#login').click();
+ cy.get('#menu_item_logout').click();
+
+ // Login to copied user then logout
+ cy.get('#user_name').type('cypressUserCopy');
+ cy.get('#user_password').type('newPassword');
+ cy.get('#login').click();
+ cy.get('#menu_item_logout').click();
+
+ // Login to admin user again and navigate to user table
+ cy.login();
+ cy.intercept('POST', '/ops/accordion_select?id=rbac_accord').as('accordion');
+ cy.menu('Settings', 'Application Settings');
+ cy.get('#settings_server > :nth-child(5)');
+ cy.get('#control_rbac_accord > .panel-title > .collapsed').click();
+ cy.wait('@accordion'); // Wait for explorer screen to load
+ cy.get('[title="View Users"] > .content_value').click({force: true});
+
+ // Find the copied user in the table and click it
+ cy.get('.clickable-row').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ if (rows[index].children[1].textContent === 'Cypress Test Copy') {
+ cy.get(rows[index].children[1]).click({ force: true });
+ }
+ });
+ });
+
+ // Verify that the copied user was created with the correct values on the summary page
+ cy.get(':nth-child(1) > .label_header').contains('ID');
+ cy.get(':nth-child(2) > .label_header').contains('Full Name');
+ cy.get(':nth-child(2) > .content_value').contains('Cypress Test Copy');
+ cy.get(':nth-child(3) > .label_header').contains('Username');
+ cy.get(':nth-child(3) > .content_value').contains('cypressUserCopy');
+ cy.get(':nth-child(4) > .label_header').contains('E-mail Address');
+ cy.get(':nth-child(4) > .content_value').contains('test_copy@email.com');
+ cy.get(':nth-child(5) > .label_header').contains('Current Group');
+ cy.get(':nth-child(5) > .content_value').contains('EvmGroup-approver');
+ cy.get(':nth-child(6) > .label_header').contains('Role');
+ cy.get(':nth-child(6) > .content_value').contains('EvmRole-approver');
+ cy.get('.label_header > .bx--link > .content > .expand').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ expect(groups.includes(rows[index].innerText)).to.eq(true);
+ });
+ });
+
+ // Click the delete user button for the copied user
+ cy.get('#user_vmdb_choice').click();
+ cy.get(':nth-child(3) > .bx--overflow-menu-options__btn').click();
+
+ // Find the new user in the table and click it
+ cy.get('.clickable-row').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ if (rows[index].children[1].textContent === 'Cypress Test Add 2') {
+ cy.get(rows[index].children[1]).click({ force: true });
+ }
+ });
+ });
+
+ // Verify that the new user was created with the correct values on the summary page
+ cy.get(':nth-child(1) > .label_header').contains('ID');
+ cy.get(':nth-child(2) > .label_header').contains('Full Name');
+ cy.get(':nth-child(2) > .content_value').contains('Cypress Test Add 2');
+ cy.get(':nth-child(3) > .label_header').contains('Username');
+ cy.get(':nth-child(3) > .content_value').contains('cypressUserAdd2');
+ cy.get(':nth-child(4) > .label_header').contains('E-mail Address');
+ cy.get(':nth-child(4) > .content_value').contains('test@email.com');
+ cy.get(':nth-child(5) > .label_header').contains('Current Group');
+ cy.get(':nth-child(5) > .content_value').contains('EvmGroup-administrator');
+ cy.get(':nth-child(6) > .label_header').contains('Role');
+ cy.get(':nth-child(6) > .content_value').contains('EvmRole-administrator');
+ cy.get('.label_header > .bx--link > .content > .expand').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ expect(groups.includes(rows[index].innerText)).to.eq(true);
+ });
+ });
+
+ // Click the delete user button for the new user
+ cy.get('#user_vmdb_choice').click();
+ cy.get(':nth-child(3) > .bx--overflow-menu-options__btn').click();
+
+ // Verify that the user was deleted from the table
+ cy.get('.clickable-row').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ expect(rows[index].children[1].textContent).to.not.eq('Cypress Test Add 2');
+ expect(rows[index].children[1].textContent).to.not.eq('Cypress Test Edit 2');
+ });
+ });
+ });
+
+ it('Test Form Validation', () => {
+ let groups = [];
+
+ // Navigate to user list and click Add a new User button
+ cy.get('[title="View Users"] > .content_value').click({force: true});
+ cy.get('#user_vmdb_choice').click();
+ cy.get(':nth-child(1) > .bx--overflow-menu-options__btn').click();
+
+ // Input values on the user form
+ cy.get('#downshift-0-toggle-button').click();
+ cy.get('#downshift-0-menu').then((val) => {
+ cy.get(val[0].children[0]).click();
+ groups.push(val[0].children[0].innerText);
+ });
+ cy.get('#selected-groups > div > p').then((selectedGroups) => {
+ const nums = [...Array(selectedGroups.length).keys()];
+ nums.forEach((index) => {
+ expect(groups.includes(selectedGroups[index].textContent)).to.eq(true);
+ });
+ });
+ cy.get('#downshift-0-toggle-button').click();
+ cy.get('#userid').type('cypressUserValidation', {force: true});
+ cy.get('#password').type('cypressPass'); // Test password validation with non-matching passwords
+ cy.get('#confirmPassword').type('incorrectPassword');
+ cy.get('#email').type('emailError'); // Test email validation with bad email
+ cy.get('#name').type('Cypress Test Validation', {force: true});
+ cy.get('#confirmPassword-error-msg');
+ cy.get('#email-error-msg');
+ cy.get('.bx--btn-set > .bx--btn--primary').should('be.disabled'); // Verify that the add button is disabled
+
+ cy.get('#email').clear().type('test@email.com'); // Input correct email
+ cy.get('#confirmPassword-error-msg'); // Verify that the confirm password error message is present
+ cy.get('.bx--btn-set > .bx--btn--primary').should('be.disabled'); // Verify that the add button is disabled
+
+ cy.get('#confirmPassword').clear().type('newPassword'); // Enter new matching password and confirm password
+ cy.get('#password').clear().type('newPassword');
+
+ cy.get('#email').clear();
+
+ // Verify that add button is enabled and click it
+ cy.get('.bx--btn-set > .bx--btn--primary').should('be.enabled');
+ cy.get('.bx--btn-set > .bx--btn--primary').click();
+
+ // Find the new user in the table and click it
+ cy.get('.clickable-row').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ if (rows[index].children[1].textContent === 'Cypress Test Validation') {
+ cy.get(rows[index].children[1]).click({ force: true });
+ }
+ });
+ });
+
+ // Verify that the new user was created with the correct values on the summary page
+ cy.get(':nth-child(1) > .label_header').contains('ID');
+ cy.get(':nth-child(2) > .label_header').contains('Full Name');
+ cy.get(':nth-child(2) > .content_value').contains('Cypress Test Validation');
+ cy.get(':nth-child(3) > .label_header').contains('Username');
+ cy.get(':nth-child(3) > .content_value').contains('cypressUserValidation');
+ cy.get(':nth-child(4) > .label_header').contains('E-mail Address');
+ cy.get(':nth-child(4) > .content_value').then((val) => {
+ expect(val[0].innerText).to.eq('');
+ });
+ cy.get(':nth-child(5) > .label_header').contains('Current Group');
+ cy.get(':nth-child(5) > .content_value').contains('EvmGroup-administrator');
+ cy.get(':nth-child(6) > .label_header').contains('Role');
+ cy.get(':nth-child(6) > .content_value').contains('EvmRole-administrator');
+ cy.get('.label_header > .bx--link > .content > .expand').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ expect(groups.includes(rows[index].innerText)).to.eq(true);
+ });
+ });
+
+ // Logout of admin user and login to the new user account and logout again
+ cy.get('#menu_item_logout').click();
+ cy.get('#user_name').type('cypressUserValidation');
+ cy.get('#user_password').type('newPassword');
+ cy.get('#login').click();
+ cy.get('#menu_item_logout').click();
+
+ // Login to admin user again and navigate to user table
+ cy.login();
+ cy.intercept('POST', '/ops/accordion_select?id=rbac_accord').as('accordion');
+ cy.menu('Settings', 'Application Settings');
+ cy.get('#settings_server > :nth-child(5)');
+ cy.get('#control_rbac_accord > .panel-title > .collapsed').click();
+ cy.wait('@accordion'); // Wait for explorer screen to load
+ cy.get('[title="View Users"] > .content_value').click({force: true});
+
+ // Find the new user in the table and click on that row
+ cy.get('.clickable-row').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ if (rows[index].children[1].textContent === 'Cypress Test Validation') {
+ cy.get(rows[index].children[1]).click({ force: true });
+ }
+ });
+ });
+
+ // Click the edit user button
+ cy.get('#user_vmdb_choice').click();
+ cy.get(':nth-child(1) > .bx--overflow-menu-options__btn').click();
+
+ // Edit the name field and confirm save button is enabled
+ cy.get('#name').type(' Edit', { force: true });
+ cy.get('.bx--btn-set > .bx--btn--primary').should('be.enabled');
+
+ // Verify that the password field is disabled and then after clicking the edit password button is enabled
+ cy.get('#passwordPlaceholder').should('be.disabled');
+ cy.get('.bx--col-sm-1 > .bx--btn').click();
+ cy.get('.bx--btn-set > .bx--btn--primary').should('be.disabled');
+
+ // Type in matching passwords and verify save button is enabled
+ cy.get('#password').type('test');
+ cy.get('#confirmPassword').type('test');
+ cy.get('.bx--btn-set > .bx--btn--primary').should('be.enabled');
+
+ // Clear confirm password field and verify save button is disabled
+ cy.get('#confirmPassword').clear();
+ cy.get('.bx--btn-set > .bx--btn--primary').should('be.disabled');
+
+ // Type incorrect value for confirm password field and verify error message is present and save button is still disabled
+ cy.get('#confirmPassword').type('fail');
+ cy.get('#email').type('test@email.com').clear();
+ cy.get('#confirmPassword-error-msg');
+ cy.get('.bx--btn-set > .bx--btn--primary').should('be.disabled');
+
+ // Click the cancel edit password button and verify that the save button is enabled and click it
+ cy.get('.bx--col-sm-1 > .bx--btn').click();
+ cy.get('.bx--btn-set > .bx--btn--primary').should('be.enabled');
+ cy.get('.bx--btn-set > .bx--btn--primary').click();
+
+ // Verify that the user values were edited with the correct values on the summary page
+ cy.get(':nth-child(1) > .label_header').contains('ID');
+ cy.get(':nth-child(2) > .label_header').contains('Full Name');
+ cy.get(':nth-child(2) > .content_value').contains('Cypress Test Validation Edit');
+ cy.get(':nth-child(3) > .label_header').contains('Username');
+ cy.get(':nth-child(3) > .content_value').contains('cypressUserValidation');
+ cy.get(':nth-child(4) > .label_header').contains('E-mail Address');
+ cy.get(':nth-child(4) > .content_value').then((val) => {
+ expect(val[0].innerText).to.eq('');
+ });
+ cy.get(':nth-child(5) > .label_header').contains('Current Group');
+ cy.get(':nth-child(5) > .content_value').contains('EvmGroup-administrator');
+ cy.get(':nth-child(6) > .label_header').contains('Role');
+ cy.get(':nth-child(6) > .content_value').contains('EvmRole-administrator');
+ cy.get('.label_header > .bx--link > .content > .expand').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ expect(groups.includes(rows[index].innerText)).to.eq(true);
+ });
+ });
+
+ // Logout of admin user and login to the edited account and logout again
+ cy.get('#menu_item_logout').click();
+ cy.get('#user_name').type('cypressUserValidation');
+ cy.get('#user_password').type('newPassword');
+ cy.get('#login').click();
+ cy.get('#menu_item_logout').click();
+
+ // Login to admin user again and navigate to user table
+ cy.login();
+ cy.intercept('POST', '/ops/accordion_select?id=rbac_accord').as('accordion');
+ cy.menu('Settings', 'Application Settings');
+ cy.get('#settings_server > :nth-child(5)');
+ cy.get('#control_rbac_accord > .panel-title > .collapsed').click();
+ cy.wait('@accordion'); // Wait for explorer screen to load
+ cy.get('[title="View Users"] > .content_value').click({force: true});
+
+ // Find the editted user in the table and click it
+ cy.get('.clickable-row').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ if (rows[index].children[1].textContent === 'Cypress Test Validation Edit') {
+ cy.get(rows[index].children[1]).click({ force: true });
+ }
+ });
+ });
+
+ // Click the edit user button
+ cy.get('#user_vmdb_choice').click();
+ cy.get(':nth-child(1) > .bx--overflow-menu-options__btn').click();
+
+ // Enter new matching passwords and click the save button
+ cy.get('#passwordPlaceholder').should('be.disabled');
+ cy.get('.bx--col-sm-1 > .bx--btn').click();
+ cy.get('.bx--btn-set > .bx--btn--primary').should('be.disabled');
+ cy.get('#password').type('test');
+ cy.get('#confirmPassword').type('test');
+ cy.get('.bx--btn-set > .bx--btn--primary').should('be.enabled');
+ cy.get('.bx--btn-set > .bx--btn--primary').click();
+
+ // Verify that the user values remain the same on the summary page
+ cy.get(':nth-child(1) > .label_header').contains('ID');
+ cy.get(':nth-child(2) > .label_header').contains('Full Name');
+ cy.get(':nth-child(2) > .content_value').contains('Cypress Test Validation Edit');
+ cy.get(':nth-child(3) > .label_header').contains('Username');
+ cy.get(':nth-child(3) > .content_value').contains('cypressUserValidation');
+ cy.get(':nth-child(4) > .label_header').contains('E-mail Address');
+ cy.get(':nth-child(4) > .content_value').then((val) => {
+ expect(val[0].innerText).to.eq('');
+ });
+ cy.get(':nth-child(5) > .label_header').contains('Current Group');
+ cy.get(':nth-child(5) > .content_value').contains('EvmGroup-administrator');
+ cy.get(':nth-child(6) > .label_header').contains('Role');
+ cy.get(':nth-child(6) > .content_value').contains('EvmRole-administrator');
+ cy.get('.label_header > .bx--link > .content > .expand').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ expect(groups.includes(rows[index].innerText)).to.eq(true);
+ });
+ });
+
+ // Logout of admin user and login to the edited account with a new password and logout again
+ cy.get('#menu_item_logout').click();
+ cy.get('#user_name').type('cypressUserValidation');
+ cy.get('#user_password').type('test');
+ cy.get('#login').click();
+ cy.get('#menu_item_logout').click();
+
+ // Login to admin user again and navigate to user table
+ cy.login();
+ cy.intercept('POST', '/ops/accordion_select?id=rbac_accord').as('accordion');
+ cy.menu('Settings', 'Application Settings');
+ cy.get('#settings_server > :nth-child(5)');
+ cy.get('#control_rbac_accord > .panel-title > .collapsed').click();
+ cy.wait('@accordion'); // Wait for explorer screen to load
+ cy.get('[title="View Users"] > .content_value').click({force: true});
+
+ // Find the editted user in the table and click it
+ cy.get('.clickable-row').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ if (rows[index].children[1].textContent === 'Cypress Test Validation Edit') {
+ cy.get(rows[index].children[1]).click({ force: true });
+ }
+ });
+ });
+
+ // Click the delete user button
+ cy.get('#user_vmdb_choice').click();
+ cy.get(':nth-child(3) > .bx--overflow-menu-options__btn').click();
+
+ // Verify that the user was deleted from the table
+ cy.get('.clickable-row').then((rows) => {
+ const nums = [...Array(rows.length).keys()];
+ nums.forEach((index) => {
+ expect(rows[index].children[1].textContent).to.not.eq('Cypress Test Validation');
+ expect(rows[index].children[1].textContent).to.not.eq('Cypress Test Validation Edit');
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/controllers/ops_controller/ops_rbac_spec.rb b/spec/controllers/ops_controller/ops_rbac_spec.rb
index ca06f4bd54c..66381224ac5 100644
--- a/spec/controllers/ops_controller/ops_rbac_spec.rb
+++ b/spec/controllers/ops_controller/ops_rbac_spec.rb
@@ -646,33 +646,6 @@
subject { controller.instance_variable_get(:@edit)[:new][:group] }
- context 'adding a new user' do
- let(:rec_type) { "user" }
- let(:params) { {:id => "new", :chosen_group => "17,7"} }
-
- it 'sets list of selected groups properly' do
- controller.send(:rbac_field_changed, rec_type)
- expect(subject).to eq([7, 17])
- end
-
- it 'sets session[:changed] to true' do
- controller.send(:rbac_field_changed, rec_type)
- expect(session[:changed]).to be(true)
- end
-
- context 'deleting name and/or password from the form while adding user' do
- let(:params) { {:name => '', :id => 'new', :password => '', :verify => ''} }
- let(:edit) { {:new => {:name => 'new_user', :password => 'passw', :verify => 'passw'}, :current => edit_curr } }
- let(:edit_curr) { {:name => nil, :group => [], :password => nil, :verify => nil} }
-
- it 'sets @edit[:new] and session[:changed] properly' do
- controller.send(:rbac_field_changed, rec_type)
- expect(controller.instance_variable_get(:@edit)[:new]).to eq(edit_curr)
- expect(session[:changed]).to be(false)
- end
- end
- end
-
context 'adding a new group' do
let(:rec_type) { "group" }
@@ -784,21 +757,4 @@
end
end
end
-
- describe '#rbac_user_set_form_vars' do
- let(:user) { FactoryBot.create(:user, :miq_groups => [group2, group1]) }
- let!(:group1) { FactoryBot.create(:miq_group) }
- let!(:group2) { FactoryBot.create(:miq_group) }
-
- before do
- allow(Rbac).to receive(:filtered).and_return([])
- controller.instance_variable_set(:@record, user)
- controller.instance_variable_set(:@sb, {})
- end
-
- it 'sorts the ids of available groups of a user' do
- controller.send(:rbac_user_set_form_vars)
- expect(controller.instance_variable_get(:@edit)[:current][:group]).to eq([group1.id, group2.id])
- end
- end
end
diff --git a/spec/controllers/ops_controller/ops_rbac_user_spec.rb b/spec/controllers/ops_controller/ops_rbac_user_spec.rb
deleted file mode 100644
index 702073ae0f4..00000000000
--- a/spec/controllers/ops_controller/ops_rbac_user_spec.rb
+++ /dev/null
@@ -1,170 +0,0 @@
-describe OpsController do
- include Spec::Support::OpsUserHelper
-
- before do
- EvmSpecHelper.local_miq_server
- MiqRegion.seed
- stub_admin
-
- controller.instance_variable_set(:@sb, {})
- allow(controller).to receive(:replace_right_cell)
- allow(controller).to receive(:load_edit).and_return(true)
- allow(controller).to receive(:render_flash)
- allow(controller).to receive(:get_node_info)
- end
-
- context 'set record data before calling record.valid?' do
- let(:group) { FactoryBot.create(:miq_group) }
-
- it "calls both record.valid? and rbac_user_set_record_vars or neither" do
- new_user_edit(
- :name => 'Full name',
- :userid => 'username',
- :group => group.id.to_s,
- :password => "foo",
- :verify => "bar",
- )
-
- user = FactoryBot.build(:user)
- allow(User).to receive(:new).and_return(user)
-
- done_valid = false
- allow(user).to receive(:valid?) {
- done_valid = true
- }
-
- done_set = false
- allow(controller).to receive(:rbac_user_set_record_vars) {
- done_set = true
- }
-
- controller.send(:rbac_edit_save_or_add, 'user')
-
- expect(done_valid).to eq(done_set)
- end
-
- it "displays both validation failures from rails and from rbac_user_validate? at the same time" do
- new_user_edit(
- :name => '', # fails user.valid?
- :userid => 'username',
- :group => group.id.to_s,
- :password => "foo", # fails rbac_user_validate
- :verify => "bar",
- )
-
- controller.send(:rbac_edit_save_or_add, 'user')
-
- messages = controller.instance_variable_get(:@flash_array).pluck(:message)
- expect(messages).to include(match(/password/i))
- expect(messages).to include(match(/name/i))
- end
- end
-
- context 'don\'t change groups on cancel' do
- let(:user) { FactoryBot.create(:user_with_group) }
- let(:group) { FactoryBot.create(:miq_group) }
-
- it "should not unset groups on cancel" do
- old_groups = user.miq_groups.pluck(:id).sort
- existing_user_edit(user, :group => "")
-
- controller.params = {:typ => nil,
- :button => 'save', # attempt to save
- :id => user.id}
- controller.send(:rbac_edit_save_or_add, 'user')
-
- # make sure it complains about the unset group in the first place
- messages = controller.instance_variable_get(:@flash_array).pluck(:message)
- expect(messages).to include(match(/group/i))
-
- # make sure the group didn't get changed
- user.reload
- expect(user.miq_groups.pluck(:id).sort).to eq(old_groups)
- end
-
- it "should not change groups when rails validation fails" do
- old_groups = user.miq_groups.pluck(:id).sort
- existing_user_edit(user, :group => group.id.to_s,
- :name => "") # fails record.valid?
-
- controller.params = {:typ => nil,
- :button => 'save',
- :id => user.id}
- controller.send(:rbac_edit_save_or_add, 'user')
-
- # make sure it complains about the name
- messages = controller.instance_variable_get(:@flash_array).pluck(:message)
- expect(messages).to include(match(/name/i))
-
- # make sure the group didn't get changed
- user.reload
- expect(user.miq_groups.pluck(:id).sort).to eq(old_groups)
- end
- end
-
- context 'update record fields when editing' do
- let(:user) { FactoryBot.create(:user_with_group) }
-
- it "updates record even for existing users" do
- existing_user_edit(user, :name => "changed")
-
- controller.params = {:typ => nil,
- :button => 'save',
- :id => user.id}
-
- controller.send(:rbac_edit_save_or_add, 'user')
-
- # make sure it returned success
- messages = controller.instance_variable_get(:@flash_array).pluck(:message)
- expect(messages).to include(match(/was saved/i))
-
- # make sure the name did get changed
- user.reload
- expect(user.name).to eq('changed')
- end
- end
-
- context 'set current_group' do
- let(:user) { FactoryBot.create(:user_with_group) }
- let(:group) { FactoryBot.create(:miq_group) }
-
- it "should set current_group for new item" do
- new_user_edit(
- :name => 'Full name',
- :userid => 'username',
- :group => group.id.to_s,
- :password => "foo",
- :verify => "foo",
- )
-
- controller.params = {:typ => nil,
- :button => 'add'}
- controller.send(:rbac_edit_save_or_add, 'user')
-
- # make sure it returned success
- messages = controller.instance_variable_get(:@flash_array).pluck(:message)
- expect(messages).to include(match(/was saved/i))
-
- # make sure current_group is set and saved
- new_user = User.where(:userid => 'username').first
- expect(new_user.current_group.id).to eq(group.id)
- end
-
- it "should set current_group when editing" do
- existing_user_edit(user, :group => group.id.to_s)
-
- controller.params = {:typ => nil,
- :button => 'save',
- :id => user.id}
- controller.send(:rbac_edit_save_or_add, 'user')
-
- # make sure it returned success
- messages = controller.instance_variable_get(:@flash_array).pluck(:message)
- expect(messages).to include(match(/was saved/i))
-
- # make sure current_group is set and saved
- user.reload
- expect(user.current_group.id).to eq(group.id)
- end
- end
-end
diff --git a/spec/controllers/ops_controller_spec.rb b/spec/controllers/ops_controller_spec.rb
index e2e709e1501..6968ed6a484 100644
--- a/spec/controllers/ops_controller_spec.rb
+++ b/spec/controllers/ops_controller_spec.rb
@@ -56,89 +56,6 @@
end
end
- describe 'rbac_user_edit' do
- let(:group) { user.miq_groups.first }
- before do
- ApplicationController.handle_exceptions = true
- end
-
- it 'can add a user w/ group' do
- session[:edit] = {
- :key => 'rbac_user_edit__new',
- :current => {},
- :new => {
- :name => 'test7',
- :userid => 'test7',
- :email => 'test7@foo.bar',
- :group => group.id.to_s,
- :password => 'test7',
- :verify => 'test7',
- }
- }
- expect(controller).to receive(:replace_right_cell)
- get :rbac_user_edit, :params => {:button => 'add'}
- end
-
- it 'cannot add a user w/o matching passwords' do
- session[:edit] = {
- :key => 'rbac_user_edit__new',
- :new => {
- :name => 'test7',
- :userid => 'test7',
- :email => 'test7@foo.bar',
- :group => group.id.to_s,
- :password => 'test7',
- :verify => 'test8',
- }
- }
-
- expect(controller).to receive(:render_flash)
- get :rbac_user_edit, :params => {:button => 'add'}
- flash_messages = assigns(:flash_array)
- expect(flash_messages.first[:message]).to eq("Password/Verify Password do not match")
- expect(flash_messages.first[:level]).to eq(:error)
- end
-
- it 'cannot add a user w/o group' do
- session[:edit] = {
- :key => 'rbac_user_edit__new',
- :new => {
- :name => 'test7',
- :userid => 'test7',
- :email => 'test7@foo.bar',
- :group => nil,
- :password => 'test7',
- :verify => 'test7',
- }
- }
-
- expect(controller).to receive(:render_flash)
- get :rbac_user_edit, :params => {:button => 'add'}
- flash_messages = assigns(:flash_array)
- expect(flash_messages.first[:message]).to eq("A User must be assigned to a Group")
- expect(flash_messages.first[:level]).to eq(:error)
- end
-
- it 'does not update the user without validation' do
- user1 = FactoryBot.create(:user, :name => "User1", :userid => "User1", :miq_groups => [group], :email => "user1@test.com")
-
- session[:edit] = {:key => "rbac_user_edit__#{user1.id}",
- :new => {:name => 'test8',
- :userid => 'test8',
- :email => 'test8@foo.bar',
- :group => nil,
- :password => 'test8',
- :verify => 'test8'}}
- expect(controller).to receive(:render_flash)
- post :rbac_user_edit, :params => {:button => 'save', :id => user1.id}
- flash_messages = assigns(:flash_array)
- expect(flash_messages.first[:message]).to eq("A User must be assigned to a Group")
- expect(flash_messages.first[:level]).to eq(:error)
- expect(user1.miq_groups).to eq([group])
- expect(user1.name).to eq('User1')
- end
- end
-
describe "#edit_changed?" do
it "should set session[:changed] as false" do
edit = {
diff --git a/spec/routing/ops_routing_spec.rb b/spec/routing/ops_routing_spec.rb
index 00ef106fc24..263a8523041 100644
--- a/spec/routing/ops_routing_spec.rb
+++ b/spec/routing/ops_routing_spec.rb
@@ -61,7 +61,6 @@
rbac_tags_edit
rbac_tenants_list
rbac_user_edit
- rbac_user_field_changed
rbac_users_list
region_edit
restart_server
diff --git a/spec/views/ops/_rbac_user_details.html.haml_spec.rb b/spec/views/ops/_rbac_user_details.html.haml_spec.rb
deleted file mode 100644
index b7667f36edc..00000000000
--- a/spec/views/ops/_rbac_user_details.html.haml_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-describe 'ops/_rbac_user_details.html.haml' do
- context "edit user" do
- before do
- user = FactoryBot.build(:user_with_group, :name => "Joe Test", :userid => "tester")
- allow(view).to receive(:current_tenant).and_return(Tenant.seed)
- allow(view).to receive(:session).and_return(:assigned_filters => [])
- edit = {:new => {:name => user.name,
- :email => user.email,
- :userid => user.userid},
- :groups => []}
- view.instance_variable_set(:@edit, edit)
- end
-
- it "displays full name" do
- render :template => "ops/_rbac_user_details"
- expect(rendered).to have_field("name", :with => "Joe Test")
- end
- end
-end