From 49f1e1be047459a27ec4d899cf736e65c5830d92 Mon Sep 17 00:00:00 2001 From: MelsHyrule Date: Mon, 10 Apr 2023 12:37:31 -0400 Subject: [PATCH] Host page react conversion --- app/controllers/host_controller.rb | 78 - .../host-edit-form/editing-context.js | 3 + .../host-edit-form/host-edit-form.jsx | 133 + .../host-edit-form/host-edit-form.schema.js | 58 + .../validate-host-credentials.jsx | 43 + .../controllers/host/host_form_controller.js | 187 - app/javascript/oldjs/controllers/index.js | 1 - .../packs/component-definitions-common.js | 2 + .../__snapshots__/host-edit-form.spec.js.snap | 6525 +++++++++++++++++ .../spec/host-edit-form/helper-data.js | 174 + .../host-edit-form/host-edit-form.spec.js | 80 + app/views/host/_form.html.haml | 121 +- .../angular/_multi_auth_credentials.html.haml | 51 - config/routes.rb | 1 - spec/controllers/host_controller_spec.rb | 47 - .../host/host_form_controller_spec.js | 182 - spec/routing/host_routing_spec.rb | 19 +- spec/views/host/_form.html.haml_spec.rb | 23 - 18 files changed, 7050 insertions(+), 678 deletions(-) create mode 100644 app/javascript/components/host-edit-form/editing-context.js create mode 100644 app/javascript/components/host-edit-form/host-edit-form.jsx create mode 100644 app/javascript/components/host-edit-form/host-edit-form.schema.js create mode 100644 app/javascript/components/host-edit-form/validate-host-credentials.jsx delete mode 100644 app/javascript/oldjs/controllers/host/host_form_controller.js create mode 100644 app/javascript/spec/host-edit-form/__snapshots__/host-edit-form.spec.js.snap create mode 100644 app/javascript/spec/host-edit-form/helper-data.js create mode 100644 app/javascript/spec/host-edit-form/host-edit-form.spec.js delete mode 100644 spec/javascripts/controllers/host/host_form_controller_spec.js delete mode 100644 spec/views/host/_form.html.haml_spec.rb diff --git a/app/controllers/host_controller.rb b/app/controllers/host_controller.rb index 97c8d832648..ba56f2251e3 100644 --- a/app/controllers/host_controller.rb +++ b/app/controllers/host_controller.rb @@ -161,84 +161,6 @@ def edit end end - def update - assert_privileges("host_edit") - case params[:button] - when "cancel" - if session[:host_items] # canceling editing credentials for multiple Hosts - flash_and_redirect(_("Edit of credentials for selected Hosts was cancelled by the user")) - else - @host = find_record_with_rbac(Host, params[:id]) - flash_and_redirect(_("Edit of Host \"%{name}\" was cancelled by the user") % {:name => @host.name}) - end - - when "save" - if session[:host_items].nil? - @host = find_record_with_rbac(Host, params[:id]) - old_host_attributes = @host.attributes.clone - valid_host = find_record_with_rbac(Host, params[:id]) - set_record_vars(valid_host, :validate) # Set the record variables, but don't save - if valid_record? && set_record_vars(@host) && @host.save - AuditEvent.success(build_saved_audit(@host, :new => @host.attributes.clone, :current => old_host_attributes)) - flash_and_redirect(_("Host \"%{name}\" was saved") % {:name => @host.name}) - nil - else - @errors.each { |msg| add_flash(msg, :error) } - @host.errors.each do |error| - add_flash("#{error.attribute.to_s.capitalize} #{error.message}", :error) - end - drop_breadcrumb(:name => _("Edit Host '%{name}'") % {:name => @host.name}, :url => "/host/edit/#{@host.id}") - @in_a_form = true - javascript_flash - end - else - valid_host = find_record_with_rbac(Host, params[:validate_id].presence || session[:host_items].first.to_i) - # Set the record variables, but don't save - creds = set_credentials(valid_host, :validate) - if valid_record? - @error = Host.batch_update_authentication(session[:host_items], creds) - end - if @error || @error.blank? - flash_and_redirect(_("Credentials/Settings saved successfully")) - else - drop_breadcrumb(:name => _("Edit Host '%{name}'") % {:name => @host.name}, :url => "/host/edit/#{@host.id}") - @in_a_form = true - javascript_flash - end - end - when "reset" - params[:edittype] = @edit[:edittype] # remember the edit type - flash_to_session(_("All changes have been reset"), :warning) - @in_a_form = true - javascript_redirect(:action => 'edit', :id => @host.id.to_s) - when "validate" - verify_host = find_record_with_rbac(Host, params[:validate_id] ? params[:validate_id].to_i : params[:id]) - if session[:host_items].nil? - set_record_vars(verify_host, :validate) - else - set_credentials(verify_host, :validate) - end - @in_a_form = true - @changed = session[:changed] - require "net/ssh" - begin - verify_host.verify_credentials(params[:type], :remember_host => params.key?(:remember_host)) - rescue Net::SSH::HostKeyMismatch # Capture the Host key mismatch from the verify - render :update do |page| - page << javascript_prologue - new_url = url_for_only_path(:action => "update", :button => "validate", :type => params[:type], :remember_host => "true", :escape => false) - page << "if (confirm('#{_('The Host SSH key has changed, do you want to accept the new key?')}')) miqAjax('#{new_url}', true);" - end - return - rescue StandardError => bang - add_flash(bang.to_s, :error) - else - add_flash(_("Credential validation was successful")) - end - javascript_flash - end - end - # handle buttons pressed on the button bar def button @edit = session[:edit] # Restore @edit for adv search box diff --git a/app/javascript/components/host-edit-form/editing-context.js b/app/javascript/components/host-edit-form/editing-context.js new file mode 100644 index 00000000000..7c0127fbd6a --- /dev/null +++ b/app/javascript/components/host-edit-form/editing-context.js @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export default createContext({}); diff --git a/app/javascript/components/host-edit-form/host-edit-form.jsx b/app/javascript/components/host-edit-form/host-edit-form.jsx new file mode 100644 index 00000000000..a283ae45f17 --- /dev/null +++ b/app/javascript/components/host-edit-form/host-edit-form.jsx @@ -0,0 +1,133 @@ +/* eslint-disable camelcase */ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { keyBy } from 'lodash'; +import EditingContext from './editing-context'; +import MiqFormRenderer from '../../forms/data-driven-form'; +import createSchema from './host-edit-form.schema'; +import miqRedirectBack from '../../helpers/miq-redirect-back'; +import ProtocolSelector from '../provider-form/protocol-selector'; +import ValidateHostCredentials from './validate-host-credentials'; +import mapper from '../../forms/mappers/componentMapper'; + +const HostEditForm = ({ ids }) => { + const [{ + initialValues, isLoading, fields, + }, setState] = useState({ + isLoading: (ids.length <= 1), + fields: [], + }); + + const loadSchema = (response, appendState = {}) => { + setState((state) => ({ + ...state, + ...appendState, + isLoading: false, + initialValues: appendState, + fields: response.data.form_schema.fields, + })); + }; + + const getHostData = (id) => { + miqSparkleOn(); + API.get(`/api/hosts/${id}?expand=resources&attributes=authentications`).then((initialValues) => { + const authentications = initialValues ? keyBy(initialValues.authentications, 'authtype') : {}; + initialValues.host_validate_against = id; + const foo = { + ...initialValues, + authentications, + }; + API.options(`/api/hosts/${id}`).then((values) => loadSchema(values, foo)).then(miqSparkleOff); + }); + }; + + const emptySchema = (appendState = {}) => { + setState((state) => ({ + ...state, + ...appendState, + fields: [], + })); + }; + + const onReset = () => { + if (ids.length > 1) { + setState((state) => ({ + ...state, + initialValues: {}, + fields: [], + })); + } + }; + + useEffect(() => { + if (ids.length === 1) { + getHostData(ids[0]); + } + setState((state) => ({ ...state, isLoading: false })); + }, [ids]); + + const onSubmit = (values) => { + miqSparkleOn(); + + const resources = []; + ids.forEach((id) => { + resources.push({ authentications: values.authentications, id }); + }); + const payload = { + action: 'edit', + resources, + }; + const selectedHostId = ids.length === 1 ? ids[0] : values.host_validate_against; + const request = API.post(`/api/hosts/${selectedHostId}`, payload); + + request.then(() => { + const message = ids.length === 1 ? sprintf(__('Modification of Host %s has been successfully queued.'), values.name) + : __('Modification of multiple Hosts has been successfully queued.'); + miqRedirectBack(message, 'success', `/host/show/${selectedHostId}`); + }).catch(miqSparkleOff); + }; + + const onCancel = () => { + miqSparkleOn(); + let message = ''; + let url = ''; + if (ids.length === 1) { + message = sprintf(__(`Edit of Host "%s" was cancelled.`), initialValues.name); + url = `/host/show/${initialValues.id}`; + } else { + message = __(`Edit of Hosts was cancelled.`); + url = '/host/show_list'; + } + miqRedirectBack(message, 'success', url); + }; + + const componentMapper = { + ...mapper, + 'protocol-selector': ProtocolSelector, + 'validate-host-credentials': ValidateHostCredentials, + }; + + return !isLoading && ( + + + + ); +}; + +HostEditForm.propTypes = { + ids: PropTypes.arrayOf(PropTypes.any), +}; + +HostEditForm.defaultProps = { + ids: [], +}; + +export default HostEditForm; diff --git a/app/javascript/components/host-edit-form/host-edit-form.schema.js b/app/javascript/components/host-edit-form/host-edit-form.schema.js new file mode 100644 index 00000000000..11193dbb7fa --- /dev/null +++ b/app/javascript/components/host-edit-form/host-edit-form.schema.js @@ -0,0 +1,58 @@ +import { componentTypes, validatorTypes } from '@@ddf'; + +// Called only when multiple hosts are selected +const loadHosts = (ids) => + API.get(`/api/hosts?expand=resources&attributes=id,name&filter[]=id=[${ids}]`) + .then(({ resources }) => { + const temp = resources.map(({ id, name }) => ({ value: id, label: name })); + temp.unshift({ label: `<${__('Choose')}>`, value: '-1' }); + return temp; + }); + +const changeValue = (value, getHostData, emptySchema) => { + if (value === '-1') { + emptySchema(); + } else { + getHostData(value); + } +}; + +function createSchema(ids, endpointFields, emptySchema, getHostData) { + const fields = [ + ...(ids.length <= 1 + ? [{ + component: componentTypes.TEXT_FIELD, + name: 'name', + id: 'name', + label: __('Name:'), + isDisabled: true, + }, + { + component: componentTypes.TEXT_FIELD, + name: 'hostname', + id: 'hostname', + label: __('Hostname (or IPv4 or IPv6 address:'), + isDisabled: true, + }, + { + component: componentTypes.TEXT_FIELD, + name: 'custom_identifier', + id: 'custom_identifier', + label: __('Custom Identifier:'), + isDisabled: true, + }] : [{ + component: componentTypes.SELECT, + name: 'host_validate_against', + id: 'host_validate_against', + label: __('Select a Host to validate against'), + isRequired: true, + validate: [{ type: validatorTypes.REQUIRED }], + loadOptions: () => loadHosts(ids), + onChange: (value) => changeValue(value, getHostData, emptySchema), + }]), + ...(endpointFields || []), + ]; + return { fields }; +} + +export default createSchema; diff --git a/app/javascript/components/host-edit-form/validate-host-credentials.jsx b/app/javascript/components/host-edit-form/validate-host-credentials.jsx new file mode 100644 index 00000000000..14db40cf53d --- /dev/null +++ b/app/javascript/components/host-edit-form/validate-host-credentials.jsx @@ -0,0 +1,43 @@ +import React, { useContext } from 'react'; +import PropTypes from 'prop-types'; +import { pick } from 'lodash'; + +import AsyncCredentials from '../async-credentials/async-credentials'; +import EditingContext from './editing-context'; + +const ValidateHostCredentials = ({ ...props }) => { + const { ids, initialValues } = useContext(EditingContext); + + const asyncValidate = (fields, fieldNames) => new Promise((resolve, reject) => { + const url = initialValues.host_validate_against ? `/api/hosts/${initialValues.host_validate_against}` : `/api/hosts/${ids[0]}`; + const resource = pick(fields, fieldNames); + + API.post(url, { action: 'verify_credentials', resource }).then(({ results: [result] = [], ...single }) => { + // eslint-disable-next-line camelcase + const { task_id, success } = result || single; + // The request here can either create a background task or fail + return success ? API.wait_for_task(task_id) : Promise.reject(result); + // The wait_for_task request can succeed with valid or invalid credentials + // with the message that the task is completed successfully. Based on the + // task_results we resolve() or reject() with an unknown error. + // Any known errors are passed to the catch(), which will reject() with a + // message describing what went wrong. + }).then((result) => (result.task_results ? resolve() : reject(__('Validation failed: unknown error')))) + .catch(({ message }) => reject([__('Validation failed:'), message].join(' '))); + }); + + // The order of props is important here, because they have to be overridden + return ; +}; + +ValidateHostCredentials.propTypes = { + ...AsyncCredentials.propTypes, + asyncValidate: PropTypes.func, + validation: PropTypes.bool, +}; +ValidateHostCredentials.defaultProps = { + validation: true, + ...AsyncCredentials.defaultProps, +}; + +export default ValidateHostCredentials; diff --git a/app/javascript/oldjs/controllers/host/host_form_controller.js b/app/javascript/oldjs/controllers/host/host_form_controller.js deleted file mode 100644 index 99c5e6c27dc..00000000000 --- a/app/javascript/oldjs/controllers/host/host_form_controller.js +++ /dev/null @@ -1,187 +0,0 @@ -ManageIQ.angular.app.controller('hostFormController', ['$http', '$scope', '$attrs', 'hostFormId', 'miqService', function($http, $scope, $attrs, hostFormId, miqService) { - var init = function() { - $scope.hostModel = { - name: '', - hostname: '', - ipmi_address: '', - custom_1: '', - user_assigned_os: '', - operating_system: false, - mac_address: '', - default_userid: '', - default_password: '', - remote_userid: '', - remote_password: '', - ws_userid: '', - ws_password: '', - ipmi_userid: '', - ipmi_password: '', - validate_id: null, - }; - - $scope.modelCopy = angular.copy( $scope.hostModel ); - $scope.afterGet = false; - $scope.formId = hostFormId; - $scope.validateClicked = miqService.validateWithAjax; - $scope.formFieldsUrl = $attrs.formFieldsUrl; - $scope.updateUrl = $attrs.updateUrl; - $scope.model = 'hostModel'; - ManageIQ.angular.scope = $scope; - - if (hostFormId === 'new') { - $scope.newRecord = true; - $scope.hostModel.name = ''; - $scope.hostModel.hostname = ''; - $scope.hostModel.ipmi_address = ''; - $scope.hostModel.custom_1 = ''; - $scope.hostModel.user_assigned_os = ''; - $scope.hostModel.operating_system = false; - $scope.hostModel.mac_address = ''; - $scope.hostModel.default_userid = ''; - $scope.hostModel.default_password = ''; - $scope.hostModel.remote_userid = ''; - $scope.hostModel.remote_password = ''; - $scope.hostModel.ws_userid = ''; - $scope.hostModel.ws_password = ''; - $scope.hostModel.ipmi_userid = ''; - $scope.hostModel.ipmi_password = ''; - $scope.hostModel.validate_id = null; - $scope.afterGet = true; - } else if (hostFormId.split(',').length === 1) { - miqService.sparkleOn(); - $http.get($scope.formFieldsUrl + hostFormId) - .then(getHostFormDataComplete) - .catch(miqService.handleFailure); - } else if (hostFormId.split(',').length > 1) { - $scope.afterGet = true; - } - - $scope.currentTab = 'default'; - }; - - $scope.changeAuthTab = function(id) { - $scope.currentTab = id; - }; - - $scope.cancelClicked = function() { - miqService.sparkleOn(); - var url; - if (hostFormId.split(',').length === 1) { - url = $scope.updateUrl + hostFormId + '?button=cancel'; - } else if (hostFormId.split(',').length > 1) { - url = $scope.updateUrl + '?button=cancel'; - } - miqService.miqAjaxButton(url); - }; - - $scope.saveClicked = function() { - miqService.sparkleOn(); - if (hostFormId.split(',').length > 1) { - var url = $scope.updateUrl + '?button=save'; - } else { - var url = $scope.updateUrl + hostFormId + '?button=save'; - } - miqService.miqAjaxButton(url, true); - }; - - $scope.resetClicked = function() { - $scope.$broadcast('resetClicked'); - $scope.hostModel = angular.copy( $scope.modelCopy ); - $scope.angularForm.$setUntouched(true); - $scope.angularForm.$setPristine(true); - miqService.miqFlash('warn', __('All changes have been reset')); - }; - - $scope.isBasicInfoValid = function() { - if ($scope.currentTab === 'default' && fieldValidity('default')) { - return true; - } else if ($scope.currentTab === 'remote' && fieldValidity('remote')) { - return true; - } else if ($scope.currentTab === 'ws' && fieldValidity('ws')) { - return true; - } else if ($scope.currentTab === 'ipmi' && fieldValidity('ipmi')) { - return true; - } return false; - }; - - function fieldValidity(fieldPrefix) { - return (($scope.hostModel.hostname || $scope.hostModel.validate_id) && - ($scope.hostModel[fieldPrefix + '_userid'] !== '' && $scope.angularForm[fieldPrefix + '_userid'] !== undefined && $scope.angularForm[fieldPrefix + '_userid'].$valid && - $scope.hostModel[fieldPrefix + '_password'] !== '' && $scope.angularForm[fieldPrefix + '_password'] !== undefined && $scope.angularForm[fieldPrefix + '_password'].$valid)); - } - - $scope.canValidate = function() { - if ($scope.isBasicInfoValid() && $scope.validateFieldsDirty()) { - return true; - } - return false; - }; - - $scope.canValidateBasicInfo = function() { - if ($scope.isBasicInfoValid()) { - return true; - } - return false; - }; - - $scope.validateFieldsDirty = function() { - if (($scope.currentTab === 'default') && - (($scope.angularForm.hostname.$dirty || $scope.angularForm.validate_id.$dirty) && - $scope.angularForm.default_userid.$dirty && - $scope.angularForm.default_password.$dirty)) { - return true; - } else if (($scope.currentTab === 'remote') && - (($scope.angularForm.hostname.$dirty || $scope.angularForm.validate_id.$dirty) && - $scope.angularForm.remote_userid.$dirty && - $scope.angularForm.remote_password.$dirty)) { - return true; - } else if (($scope.currentTab === 'ws') && - (($scope.angularForm.hostname.$dirty || $scope.angularForm.validate_id.$dirty) && - $scope.angularForm.ws_userid.$dirty && - $scope.angularForm.ws_password.$dirty)) { - return true; - } else if (($scope.currentTab === 'ipmi') && - ($scope.angularForm.ipmi_address.$dirty && - $scope.angularForm.ipmi_userid.$dirty && - $scope.angularForm.ipmi_password.$dirty)) { - return true; - } return false; - }; - - function getHostFormDataComplete(response) { - var data = response.data; - - $scope.hostModel.name = data.name; - $scope.hostModel.hostname = data.hostname; - $scope.hostModel.ipmi_address = data.ipmi_address; - $scope.hostModel.custom_1 = data.custom_1; - $scope.hostModel.user_assigned_os = data.user_assigned_os; - $scope.hostModel.operating_system = data.operating_system; - $scope.hostModel.mac_address = data.mac_address; - $scope.hostModel.default_userid = data.default_userid; - $scope.hostModel.remote_userid = data.remote_userid; - $scope.hostModel.ws_userid = data.ws_userid; - $scope.hostModel.ipmi_userid = data.ipmi_userid; - $scope.hostModel.validate_id = data.validate_id; - - if ($scope.hostModel.default_userid !== '') { - $scope.hostModel.default_password = miqService.storedPasswordPlaceholder; - } - if ($scope.hostModel.remote_userid !== '') { - $scope.hostModel.remote_password = miqService.storedPasswordPlaceholder; - } - if ($scope.hostModel.ws_userid !== '') { - $scope.hostModel.ws_password = miqService.storedPasswordPlaceholder; - } - if ($scope.hostModel.ipmi_userid !== '') { - $scope.hostModel.ipmi_password = miqService.storedPasswordPlaceholder; - } - - $scope.afterGet = true; - - $scope.modelCopy = angular.copy( $scope.hostModel ); - miqService.sparkleOff(); - } - - init(); -}]); diff --git a/app/javascript/oldjs/controllers/index.js b/app/javascript/oldjs/controllers/index.js index e171a30867b..6aabd28757a 100644 --- a/app/javascript/oldjs/controllers/index.js +++ b/app/javascript/oldjs/controllers/index.js @@ -12,7 +12,6 @@ require('./dialog_user/dialog_user_controller.js'); require('./dialog_user/dialog_user_reconfigure_controller.js'); require('./ems_keypair/ems_keypair_controller.js'); require('./error_modal_controller.js'); -require('./host/host_form_controller.js'); require('./miq_ae_class/ae_inline_method_selection_controller.js'); require('./ops/pglogical_replication_form_controller.js'); require('./playbook-reusable-code-mixin.js'); diff --git a/app/javascript/packs/component-definitions-common.js b/app/javascript/packs/component-definitions-common.js index 5460c5b8330..3ff04855322 100644 --- a/app/javascript/packs/component-definitions-common.js +++ b/app/javascript/packs/component-definitions-common.js @@ -148,6 +148,7 @@ import SearchBar from '../components/search-bar'; import SettingsCompanyCategories from '../components/settings-company-categories'; import SettingsLabelTagMapping from '../components/settings-label-tag-mapping'; import VisualSettingsForm from '../components/visual-settings-form'; +import HostEditForm from '../components/host-edit-form/host-edit-form'; import HostInitiatorForm from '../components/host-initiator-form'; import HostInitiatorGroupForm from '../components/host-initiator-group-form'; import StorageServiceForm from '../components/storage-service-form'; @@ -309,6 +310,7 @@ ManageIQ.component.addReact('WorkersForm', WorkersForm); ManageIQ.component.addReact('ProvGrid', ProvGrid); ManageIQ.component.addReact('PhysicalStorageForm', PhysicalStorageForm); ManageIQ.component.addReact('VisualSettingsForm', VisualSettingsForm); +ManageIQ.component.addReact('HostEditForm', HostEditForm); ManageIQ.component.addReact('HostInitiatorForm', HostInitiatorForm); ManageIQ.component.addReact('HostInitiatorGroupForm', HostInitiatorGroupForm); ManageIQ.component.addReact('StorageServiceForm', StorageServiceForm); 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 new file mode 100644 index 00000000000..e6bebb76869 --- /dev/null +++ b/app/javascript/spec/host-edit-form/__snapshots__/host-edit-form.spec.js.snap @@ -0,0 +1,6525 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Show Edit Host Form Component should render form for *one* host 1`] = ` + + + + + + + , + , + , + , + , + ] + } + schema={ + Object { + "fields": Array [ + Object { + "component": "text-field", + "id": "name", + "isDisabled": true, + "label": "Name:", + "name": "name", + }, + Object { + "component": "text-field", + "id": "hostname", + "isDisabled": true, + "label": "Hostname (or IPv4 or IPv6 address:", + "name": "hostname", + }, + Object { + "component": "text-field", + "id": "custom_identifier", + "isDisabled": true, + "label": "Custom Identifier:", + "name": "custom_identifier", + }, + Object { + "component": "sub-form", + "fields": Array [ + Object { + "component": "tabs", + "fields": Array [ + Object { + "component": "tab-item", + "fields": Array [ + Object { + "component": "validate-host-credentials", + "fields": Array [ + Object { + "component": "text-field", + "id": "authentications.default.userid", + "isRequired": true, + "label": "Username", + "name": "authentications.default.userid", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "password-field", + "id": "authentications.default.password", + "isRequired": true, + "label": "Password", + "name": "authentications.default.password", + "type": "password", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "endpoints.default.valid", + "isRequired": true, + "name": "endpoints.default.valid", + "skipSubmit": true, + }, + ], + "id": "default-tab", + "name": "default-tab", + "title": "Default", + }, + Object { + "component": "tab-item", + "fields": Array [ + Object { + "component": "validate-host-credentials", + "fields": Array [ + Object { + "component": "text-field", + "id": "authentications.remote.userid", + "isRequired": true, + "label": "Username", + "name": "authentications.remote.userid", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "password-field", + "helperText": "Required if SSH login is disabled for the Default account.", + "id": "authentications.remote.password", + "isRequired": true, + "label": "Password", + "name": "authentications.remote.password", + "type": "password", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "endpoints.remote.valid", + "isRequired": true, + "name": "endpoints.remote.valid", + "skipSubmit": true, + }, + ], + "id": "remote-tab", + "name": "remote-tab", + "title": "Remote Login", + }, + Object { + "component": "tab-item", + "fields": Array [ + Object { + "component": "validate-host-credentials", + "fields": Array [ + Object { + "component": "text-field", + "id": "authentications.ws.userid", + "isRequired": true, + "label": "Username", + "name": "authentications.ws.userid", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "password-field", + "helperText": "Used for access to Web Services.", + "id": "authentications.ws.password", + "isRequired": true, + "label": "Password", + "name": "authentications.ws.password", + "type": "password", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "endpoints.ws.valid", + "isRequired": true, + "name": "endpoints.ws.valid", + "skipSubmit": true, + }, + ], + "id": "ws-tab", + "name": "ws-tab", + "title": "Web Service", + }, + ], + "name": "tabs", + }, + ], + "id": "endpoints-subform", + "name": "endpoints-subform", + "title": "Endpoints", + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + > + , + , + , + , + , + ] + } + formWrapperProps={ + Object { + "className": "form-react", + } + } + resetLabel="Reset" + schema={ + Object { + "fields": Array [ + Object { + "component": "text-field", + "id": "name", + "isDisabled": true, + "label": "Name:", + "name": "name", + }, + Object { + "component": "text-field", + "id": "hostname", + "isDisabled": true, + "label": "Hostname (or IPv4 or IPv6 address:", + "name": "hostname", + }, + Object { + "component": "text-field", + "id": "custom_identifier", + "isDisabled": true, + "label": "Custom Identifier:", + "name": "custom_identifier", + }, + Object { + "component": "sub-form", + "fields": Array [ + Object { + "component": "tabs", + "fields": Array [ + Object { + "component": "tab-item", + "fields": Array [ + Object { + "component": "validate-host-credentials", + "fields": Array [ + Object { + "component": "text-field", + "id": "authentications.default.userid", + "isRequired": true, + "label": "Username", + "name": "authentications.default.userid", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "password-field", + "id": "authentications.default.password", + "isRequired": true, + "label": "Password", + "name": "authentications.default.password", + "type": "password", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "endpoints.default.valid", + "isRequired": true, + "name": "endpoints.default.valid", + "skipSubmit": true, + }, + ], + "id": "default-tab", + "name": "default-tab", + "title": "Default", + }, + Object { + "component": "tab-item", + "fields": Array [ + Object { + "component": "validate-host-credentials", + "fields": Array [ + Object { + "component": "text-field", + "id": "authentications.remote.userid", + "isRequired": true, + "label": "Username", + "name": "authentications.remote.userid", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "password-field", + "helperText": "Required if SSH login is disabled for the Default account.", + "id": "authentications.remote.password", + "isRequired": true, + "label": "Password", + "name": "authentications.remote.password", + "type": "password", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "endpoints.remote.valid", + "isRequired": true, + "name": "endpoints.remote.valid", + "skipSubmit": true, + }, + ], + "id": "remote-tab", + "name": "remote-tab", + "title": "Remote Login", + }, + Object { + "component": "tab-item", + "fields": Array [ + Object { + "component": "validate-host-credentials", + "fields": Array [ + Object { + "component": "text-field", + "id": "authentications.ws.userid", + "isRequired": true, + "label": "Username", + "name": "authentications.ws.userid", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "password-field", + "helperText": "Used for access to Web Services.", + "id": "authentications.ws.password", + "isRequired": true, + "label": "Password", + "name": "authentications.ws.password", + "type": "password", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "endpoints.ws.valid", + "isRequired": true, + "name": "endpoints.ws.valid", + "skipSubmit": true, + }, + ], + "id": "ws-tab", + "name": "ws-tab", + "title": "Web Service", + }, + ], + "name": "tabs", + }, + ], + "id": "endpoints-subform", + "name": "endpoints-subform", + "title": "Endpoints", + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + showFormControls={true} + submitLabel="Save" + > + , + , + , + , + , + ] + } + formWrapperProps={ + Object { + "className": "form-react", + } + } + resetLabel="Reset" + schema={ + Object { + "fields": Array [ + Object { + "component": "text-field", + "id": "name", + "isDisabled": true, + "label": "Name:", + "name": "name", + }, + Object { + "component": "text-field", + "id": "hostname", + "isDisabled": true, + "label": "Hostname (or IPv4 or IPv6 address:", + "name": "hostname", + }, + Object { + "component": "text-field", + "id": "custom_identifier", + "isDisabled": true, + "label": "Custom Identifier:", + "name": "custom_identifier", + }, + Object { + "component": "sub-form", + "fields": Array [ + Object { + "component": "tabs", + "fields": Array [ + Object { + "component": "tab-item", + "fields": Array [ + Object { + "component": "validate-host-credentials", + "fields": Array [ + Object { + "component": "text-field", + "id": "authentications.default.userid", + "isRequired": true, + "label": "Username", + "name": "authentications.default.userid", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "password-field", + "id": "authentications.default.password", + "isRequired": true, + "label": "Password", + "name": "authentications.default.password", + "type": "password", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "endpoints.default.valid", + "isRequired": true, + "name": "endpoints.default.valid", + "skipSubmit": true, + }, + ], + "id": "default-tab", + "name": "default-tab", + "title": "Default", + }, + Object { + "component": "tab-item", + "fields": Array [ + Object { + "component": "validate-host-credentials", + "fields": Array [ + Object { + "component": "text-field", + "id": "authentications.remote.userid", + "isRequired": true, + "label": "Username", + "name": "authentications.remote.userid", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "password-field", + "helperText": "Required if SSH login is disabled for the Default account.", + "id": "authentications.remote.password", + "isRequired": true, + "label": "Password", + "name": "authentications.remote.password", + "type": "password", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "endpoints.remote.valid", + "isRequired": true, + "name": "endpoints.remote.valid", + "skipSubmit": true, + }, + ], + "id": "remote-tab", + "name": "remote-tab", + "title": "Remote Login", + }, + Object { + "component": "tab-item", + "fields": Array [ + Object { + "component": "validate-host-credentials", + "fields": Array [ + Object { + "component": "text-field", + "id": "authentications.ws.userid", + "isRequired": true, + "label": "Username", + "name": "authentications.ws.userid", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "password-field", + "helperText": "Used for access to Web Services.", + "id": "authentications.ws.password", + "isRequired": true, + "label": "Password", + "name": "authentications.ws.password", + "type": "password", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "endpoints.ws.valid", + "isRequired": true, + "name": "endpoints.ws.valid", + "skipSubmit": true, + }, + ], + "id": "ws-tab", + "name": "ws-tab", + "title": "Web Service", + }, + ], + "name": "tabs", + }, + ], + "id": "endpoints-subform", + "name": "endpoints-subform", + "title": "Endpoints", + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + showFormControls={true} + submitLabel="Save" + > +
+ + + + + + + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + + + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + + + + +
+ +
+
+ +
+
+
+
+
+
+
+
+ + + + +
+
+

+ Endpoints +

+
+ + + + + +
+ +
    + +
  • + +
  • +
    + +
  • + +
  • +
    + +
  • + +
  • +
    +
+ +
+ + + +
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + , + , + , + , + , + ] + } + formSpyProps={ + Object { + "active": undefined, + "dirty": true, + "dirtyFields": Object { + "endpoints.default.valid": true, + "endpoints.remote.valid": true, + "endpoints.ws.valid": true, + }, + "dirtyFieldsSinceLastSubmit": Object { + "endpoints.default.valid": true, + "endpoints.remote.valid": true, + "endpoints.ws.valid": true, + "hostname": true, + "name": true, + }, + "dirtySinceLastSubmit": false, + "error": undefined, + "errors": Object { + "authentications": Object { + "default": Object { + "userid": "Required", + }, + "remote": Object { + "userid": "Required", + }, + "ws": Object { + "userid": "Required", + }, + }, + }, + "form": Object { + "batch": [Function], + "blur": [Function], + "change": [Function], + "destroyOnUnregister": false, + "focus": [Function], + "getFieldState": [Function], + "getRegisteredFields": [Function], + "getState": [Function], + "initialize": [Function], + "isValidationPaused": [Function], + "mutators": Object { + "concat": [Function], + "insert": [Function], + "move": [Function], + "pop": [Function], + "push": [Function], + "remove": [Function], + "removeBatch": [Function], + "shift": [Function], + "swap": [Function], + "unshift": [Function], + "update": [Function], + }, + "pauseValidation": [Function], + "registerField": [Function], + "reset": [Function], + "resetFieldState": [Function], + "restart": [Function], + "resumeValidation": [Function], + "setConfig": [Function], + "submit": [Function], + "subscribe": [Function], + }, + "hasSubmitErrors": false, + "hasValidationErrors": true, + "initialValues": Object { + "authentications": Object {}, + "ems_id": "2", + "host_validate_against": 1, + "hostname": null, + "id": "1", + "ipaddress": "10.197.65.254", + "name": "10.197.65.254", + "type": "ManageIQ::Providers::IbmPowerHmc::InfraManager::Host", + }, + "invalid": true, + "modified": Object { + "authentications.default.userid": false, + "authentications.remote.userid": false, + "authentications.ws.userid": false, + "custom_identifier": false, + "endpoints.default.valid": true, + "endpoints.remote.valid": true, + "endpoints.ws.valid": true, + "hostname": false, + "name": false, + }, + "modifiedSinceLastSubmit": false, + "pristine": false, + "submitError": undefined, + "submitErrors": undefined, + "submitFailed": false, + "submitSucceeded": false, + "submitting": false, + "touched": Object { + "authentications.default.userid": false, + "authentications.remote.userid": false, + "authentications.ws.userid": false, + "custom_identifier": false, + "endpoints.default.valid": false, + "endpoints.remote.valid": false, + "endpoints.ws.valid": false, + "hostname": false, + "name": false, + }, + "valid": false, + "validating": false, + "values": Object { + "authentications": Object {}, + "ems_id": "2", + "endpoints": Object { + "default": Object { + "valid": true, + }, + "remote": Object { + "valid": true, + }, + "ws": Object { + "valid": true, + }, + }, + "host_validate_against": 1, + "hostname": null, + "id": "1", + "ipaddress": "10.197.65.254", + "name": "10.197.65.254", + "type": "ManageIQ::Providers::IbmPowerHmc::InfraManager::Host", + }, + "visited": Object { + "authentications.default.userid": false, + "authentications.remote.userid": false, + "authentications.ws.userid": false, + "custom_identifier": false, + "endpoints.default.valid": false, + "endpoints.remote.valid": false, + "endpoints.ws.valid": false, + "hostname": false, + "name": false, + }, + } + } + onCancel={[Function]} + onReset={[Function]} + resetLabel="Reset" + schema={ + Object { + "fields": Array [ + Object { + "component": "text-field", + "id": "name", + "isDisabled": true, + "label": "Name:", + "name": "name", + }, + Object { + "component": "text-field", + "id": "hostname", + "isDisabled": true, + "label": "Hostname (or IPv4 or IPv6 address:", + "name": "hostname", + }, + Object { + "component": "text-field", + "id": "custom_identifier", + "isDisabled": true, + "label": "Custom Identifier:", + "name": "custom_identifier", + }, + Object { + "component": "sub-form", + "fields": Array [ + Object { + "component": "tabs", + "fields": Array [ + Object { + "component": "tab-item", + "fields": Array [ + Object { + "component": "validate-host-credentials", + "fields": Array [ + Object { + "component": "text-field", + "id": "authentications.default.userid", + "isRequired": true, + "label": "Username", + "name": "authentications.default.userid", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "password-field", + "id": "authentications.default.password", + "isRequired": true, + "label": "Password", + "name": "authentications.default.password", + "type": "password", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "endpoints.default.valid", + "isRequired": true, + "name": "endpoints.default.valid", + "skipSubmit": true, + }, + ], + "id": "default-tab", + "name": "default-tab", + "title": "Default", + }, + Object { + "component": "tab-item", + "fields": Array [ + Object { + "component": "validate-host-credentials", + "fields": Array [ + Object { + "component": "text-field", + "id": "authentications.remote.userid", + "isRequired": true, + "label": "Username", + "name": "authentications.remote.userid", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "password-field", + "helperText": "Required if SSH login is disabled for the Default account.", + "id": "authentications.remote.password", + "isRequired": true, + "label": "Password", + "name": "authentications.remote.password", + "type": "password", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "endpoints.remote.valid", + "isRequired": true, + "name": "endpoints.remote.valid", + "skipSubmit": true, + }, + ], + "id": "remote-tab", + "name": "remote-tab", + "title": "Remote Login", + }, + Object { + "component": "tab-item", + "fields": Array [ + Object { + "component": "validate-host-credentials", + "fields": Array [ + Object { + "component": "text-field", + "id": "authentications.ws.userid", + "isRequired": true, + "label": "Username", + "name": "authentications.ws.userid", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "password-field", + "helperText": "Used for access to Web Services.", + "id": "authentications.ws.password", + "isRequired": true, + "label": "Password", + "name": "authentications.ws.password", + "type": "password", + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + ], + "id": "endpoints.ws.valid", + "isRequired": true, + "name": "endpoints.ws.valid", + "skipSubmit": true, + }, + ], + "id": "ws-tab", + "name": "ws-tab", + "title": "Web Service", + }, + ], + "name": "tabs", + }, + ], + "id": "endpoints-subform", + "name": "endpoints-subform", + "title": "Endpoints", + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + submitLabel="Save" + > + + +
+ + + + + + + + + +
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+
+`; + +exports[`Show Edit Host Form Component should render form for multiple hosts 1`] = ` + + + + + + + , + , + ] + } + schema={ + Object { + "fields": Array [ + Object { + "component": "select", + "id": "host_validate_against", + "isRequired": true, + "label": "Select a Host to validate against", + "loadOptions": [Function], + "name": "host_validate_against", + "onChange": [Function], + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + > + , + , + ] + } + formWrapperProps={ + Object { + "className": "form-react", + } + } + resetLabel="Reset" + schema={ + Object { + "fields": Array [ + Object { + "component": "select", + "id": "host_validate_against", + "isRequired": true, + "label": "Select a Host to validate against", + "loadOptions": [Function], + "name": "host_validate_against", + "onChange": [Function], + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + showFormControls={true} + submitLabel="Save" + > + , + , + ] + } + formWrapperProps={ + Object { + "className": "form-react", + } + } + resetLabel="Reset" + schema={ + Object { + "fields": Array [ + Object { + "component": "select", + "id": "host_validate_against", + "isRequired": true, + "label": "Select a Host to validate against", + "loadOptions": [Function], + "name": "host_validate_against", + "onChange": [Function], + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + showFormControls={true} + submitLabel="Save" + > +
+ + + + + + + + + Select a Host to validate against + + } + loadOptions={[Function]} + loadOptionsChangeCounter={1} + loadingMessage="Loading..." + name="host_validate_against" + onBlur={[Function]} + onChange={[Function]} + onFocus={[Function]} + options={Array []} + placeholder="" + pluckSingleValue={true} + simpleValue={false} + value="" + > + + Select a Host to validate against + + } + name="host_validate_against" + noOptionsMessage={[Function]} + onBlur={[Function]} + onChange={[Function]} + onFocus={[Function]} + onInputChange={[Function]} + options={ + Array [ + Object { + "label": "", + "value": "-1", + }, + Object { + "label": "aramis", + "value": "9", + }, + Object { + "label": "10.197.65.254", + "value": "10", + }, + ] + } + placeholder="" + value="" + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + , + ] + } + formSpyProps={ + Object { + "active": undefined, + "dirty": false, + "dirtyFields": Object {}, + "dirtyFieldsSinceLastSubmit": Object {}, + "dirtySinceLastSubmit": false, + "error": undefined, + "errors": Object { + "host_validate_against": "Required", + }, + "form": Object { + "batch": [Function], + "blur": [Function], + "change": [Function], + "destroyOnUnregister": false, + "focus": [Function], + "getFieldState": [Function], + "getRegisteredFields": [Function], + "getState": [Function], + "initialize": [Function], + "isValidationPaused": [Function], + "mutators": Object { + "concat": [Function], + "insert": [Function], + "move": [Function], + "pop": [Function], + "push": [Function], + "remove": [Function], + "removeBatch": [Function], + "shift": [Function], + "swap": [Function], + "unshift": [Function], + "update": [Function], + }, + "pauseValidation": [Function], + "registerField": [Function], + "reset": [Function], + "resetFieldState": [Function], + "restart": [Function], + "resumeValidation": [Function], + "setConfig": [Function], + "submit": [Function], + "subscribe": [Function], + }, + "hasSubmitErrors": false, + "hasValidationErrors": true, + "initialValues": Object {}, + "invalid": true, + "modified": Object { + "host_validate_against": false, + }, + "modifiedSinceLastSubmit": false, + "pristine": true, + "submitError": undefined, + "submitErrors": undefined, + "submitFailed": false, + "submitSucceeded": false, + "submitting": false, + "touched": Object { + "host_validate_against": false, + }, + "valid": false, + "validating": false, + "values": Object {}, + "visited": Object { + "host_validate_against": false, + }, + } + } + onCancel={[Function]} + onReset={[Function]} + resetLabel="Reset" + schema={ + Object { + "fields": Array [ + Object { + "component": "select", + "id": "host_validate_against", + "isRequired": true, + "label": "Select a Host to validate against", + "loadOptions": [Function], + "name": "host_validate_against", + "onChange": [Function], + "validate": Array [ + Object { + "type": "required", + }, + ], + }, + Object { + "component": "spy-field", + "initialize": undefined, + "name": "spy-field", + }, + ], + } + } + submitLabel="Save" + > + + +
+ + + + + + + + + +
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+
+`; diff --git a/app/javascript/spec/host-edit-form/helper-data.js b/app/javascript/spec/host-edit-form/helper-data.js new file mode 100644 index 00000000000..411817d5044 --- /dev/null +++ b/app/javascript/spec/host-edit-form/helper-data.js @@ -0,0 +1,174 @@ +export const samepleInitialValues = { + id: '1', + name: '10.197.65.254', + hostname: null, + ipaddress: '10.197.65.254', + ems_id: '2', + type: 'ManageIQ::Providers::IbmPowerHmc::InfraManager::Host', +}; + +export const sampleSingleReponse = { + data: { + form_schema: { + fields: [ + { + component: 'sub-form', + id: 'endpoints-subform', + name: 'endpoints-subform', + title: 'Endpoints', + fields: [ + { + component: 'tabs', + name: 'tabs', + fields: [ + { + component: 'tab-item', + id: 'default-tab', + name: 'default-tab', + title: 'Default', + fields: [ + { + component: 'validate-host-credentials', + id: 'endpoints.default.valid', + name: 'endpoints.default.valid', + skipSubmit: true, + isRequired: true, + fields: [ + { + component: 'text-field', + id: 'authentications.default.userid', + name: 'authentications.default.userid', + label: 'Username', + isRequired: true, + validate: [ + { + type: 'required', + }, + ], + }, + { + component: 'password-field', + id: 'authentications.default.password', + name: 'authentications.default.password', + label: 'Password', + type: 'password', + isRequired: true, + validate: [ + { + type: 'required', + }, + ], + }, + ], + }, + ], + }, + { + component: 'tab-item', + id: 'remote-tab', + name: 'remote-tab', + title: 'Remote Login', + fields: [ + { + component: 'validate-host-credentials', + id: 'endpoints.remote.valid', + name: 'endpoints.remote.valid', + skipSubmit: true, + isRequired: true, + fields: [ + { + component: 'text-field', + id: 'authentications.remote.userid', + name: 'authentications.remote.userid', + label: 'Username', + isRequired: true, + validate: [ + { + type: 'required', + }, + ], + }, + { + component: 'password-field', + id: 'authentications.remote.password', + name: 'authentications.remote.password', + label: 'Password', + type: 'password', + isRequired: true, + validate: [ + { + type: 'required', + }, + ], + helperText: 'Required if SSH login is disabled for the Default account.', + }, + ], + }, + ], + }, + { + component: 'tab-item', + id: 'ws-tab', + name: 'ws-tab', + title: 'Web Service', + fields: [ + { + component: 'validate-host-credentials', + id: 'endpoints.ws.valid', + name: 'endpoints.ws.valid', + skipSubmit: true, + isRequired: true, + fields: [ + { + component: 'text-field', + id: 'authentications.ws.userid', + name: 'authentications.ws.userid', + label: 'Username', + isRequired: true, + validate: [ + { + type: 'required', + }, + ], + }, + { + component: 'password-field', + id: 'authentications.ws.password', + name: 'authentications.ws.password', + label: 'Password', + type: 'password', + isRequired: true, + validate: [ + { + type: 'required', + }, + ], + helperText: 'Used for access to Web Services.', + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + }, +}; + +export const sampleMultiResponse = { + resources: [ + { + href: 'http://localhost:3000/api/hosts/9', + id: '9', + name: 'aramis', + }, + { + href: 'http://localhost:3000/api/hosts/10', + id: '10', + name: '10.197.65.254', + }, + ], +}; diff --git a/app/javascript/spec/host-edit-form/host-edit-form.spec.js b/app/javascript/spec/host-edit-form/host-edit-form.spec.js new file mode 100644 index 00000000000..e8700efb29b --- /dev/null +++ b/app/javascript/spec/host-edit-form/host-edit-form.spec.js @@ -0,0 +1,80 @@ +import React from 'react'; +import toJson from 'enzyme-to-json'; +import fetchMock from 'fetch-mock'; +import { act } from 'react-dom/test-utils'; +import { Button } from 'carbon-components-react'; +import HostEditForm from '../../components/host-edit-form/host-edit-form'; +import { samepleInitialValues, sampleSingleReponse, sampleMultiResponse } from './helper-data'; + +import { mount, shallow } from '../helpers/mountForm'; +import '../../oldjs/miq_application'; // for miqJqueryRequest +import '../helpers/miqSparkle'; + +describe('Show Edit Host Form Component', () => { + let submitSpy; + + const id = [1]; + const ids = [1, 2, 3]; + + beforeEach(() => { + // fetchMock + // .once('/api/event_streams', sampleReponse); + // submitSpy = jest.spyOn(window, 'miqJqueryRequest'); + }); + + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + // submitSpy.mockRestore(); + }); + + /* + * Render Form + */ + + it('should render form for *one* host', async(done) => { + let wrapper; + fetchMock + .get(`/api/hosts/${id[0]}?expand=resources&attributes=authentications`, samepleInitialValues) + .mock(`/api/hosts/${id[0]}`, sampleSingleReponse); + await act(async() => { + wrapper = mount(); + }); + setImmediate(() => { + wrapper.update(); + expect(toJson(wrapper)).toMatchSnapshot(); + done(); + }); + }); + + it('should render form for multiple hosts', async(done) => { + let wrapper; + fetchMock.get(`/api/hosts?expand=resources&attributes=id,name&filter[]=id=[${ids}]`, sampleMultiResponse); + await act(async() => { + wrapper = mount(); + }); + setImmediate(() => { + wrapper.update(); + expect(toJson(wrapper)).toMatchSnapshot(); + done(); + }); + }); + + /* + * Submit Logic + */ + +// it('should not submit values when form is empty', async(done) => { +// let wrapper; +// await act(async() => { +// wrapper = mount(); +// }); +// setImmediate(() => { +// wrapper.update(); +// expect(wrapper.find(Button)).toHaveLength(1); +// wrapper.find(Button).first().simulate('click'); +// expect(submitSpy).toHaveBeenCalledTimes(0); +// done(); +// }); +// }); +}); diff --git a/app/views/host/_form.html.haml b/app/views/host/_form.html.haml index a290fb87d22..077c54739bd 100644 --- a/app/views/host/_form.html.haml +++ b/app/views/host/_form.html.haml @@ -1,107 +1,14 @@ -- @angular_form = true - -.form-horizontal - %form#form_div{"name" => "angularForm", - "ng-controller" => "hostFormController", - 'ng-cloak' => '', - "form-fields-url" => "/#{controller_name}/host_form_fields/", - "update-url" => "/#{controller_name}/update/", - "novalidate" => true} - = render :partial => "layouts/flash_msg" - %div{"ng-if" => "afterGet"} - - if session[:host_items].nil? - %div - %div - .form-group{"ng-class" => "{'has-error': angularForm.name.$invalid}"} - %label.col-md-2.control-label{"for" => "name"} - = _("Name") - .col-md-8 - %input.form-control{"type" => "text", - "id" => "name", - "name" => "name", - "ng-model" => "hostModel.name", - "maxlength" => "#{ViewHelper::MAX_NAME_LEN}", - "miqrequired" => "", - "checkchange" => "", - "auto-focus" => ""} - %span.help-block{"ng-show" => "angularForm.name.$error.miqrequired"} - = _("Required") - .form-group{"ng-class" => "{'has-error': angularForm.hostname.$invalid}"} - %label.col-md-2.control-label{"for" => "hostname"} - = _("Hostname (or IPv4 or IPv6 address)") - .col-md-4 - %input.form-control{"type" => "text", - "id" => "hostname", - "name" => "hostname", - "ng-model" => "hostModel.hostname", - "maxlength" => "#{ViewHelper::MAX_HOSTNAME_LEN}", - "miqrequired" => "", - "checkchange" => ""} - %span.help-block{"ng-show" => "angularForm.hostname.$error.miqrequired"} - = _("Required") - - .form-group{"ng-class" => "{'has-error': angularForm.user_assigned_os.$invalid}", "ng-hide" => "hostModel.operating_system"} - %label.col-md-2.control-label - = _("Host platform") - .col-md-8 - = select_tag('user_assigned_os', - options_for_select([["<#{_('Choose')}>", nil]], disabled: ["<#{_('Choose')}>", nil]), - "ng-model" => "hostModel.user_assigned_os", - "checkchange" => "", - "ng-required" => "!hostModel.operating_system", - "selectpicker-for-select-tag" => "") - %span.help-block{"ng-show" => "angularForm.user_assigned_os.$error.required"} - = _("Required") - .form-group - %label.col-md-2.control-label - = _("Custom Identifier") - .col-md-8 - %input#custom_1.form-control{"type" => "text", - "name" => "custom_1", - "ng-model" => "hostModel.custom_1", - "maxlength" => 50, - "checkchange" => ""} - .form-group{"ng-class" => "{'has-error': angularForm.ipmi_address.$error.requiredDependsOn}"} - %label.col-md-2.control-label{"for" => "ipmi_address"} - = _("IPMI IP Address") - .col-md-8 - %input.form-control#ipmi_address{"type" => "text", - "id" => "ipmi_address", - "name" => "ipmi_address", - "ng-model" => "hostModel.ipmi_address", - "required-depends-on" => "hostModel.ipmi_userid", - "required-if-exists" => "ipmi_userid", - "maxlength" => 15, - "checkchange" => ""} - %span.help-block{"ng-show" => "angularForm.ipmi_address.$error.requiredDependsOn"} - = _("Required") - .form-group - %label.col-md-2.control-label - = _("MAC Address") - .col-md-8 - %input#mac_address.form-control{"type" => "text", - "name" => "mac_address", - "ng-model" => "hostModel.mac_address", - "maxlength" => "#{ViewHelper::MAX_NAME_LEN}", - "checkchange" => ""} - %hr - = render(:partial => "/layouts/angular/multi_auth_credentials", - :locals => {:record => @host, :ng_model => "hostModel"}) - = render :partial => "layouts/angular/x_edit_buttons_angular" - - - unless session[:host_items].nil? - %h3 - = n_("Host", "Hosts", session[:host_items].length) - = _('Selected') - = _('Click on a Host to fetch its settings') - %table.admittable{:height => '75'} - %tbody - %tr - %td - - if session[:host_items] - - @embedded = true - = render :partial => 'layouts/gtl' - -:javascript - ManageIQ.angular.app.value('hostFormId', '#{(@host.id || (session[:host_items] && session[:host_items].join(",")))}'); - miq_bootstrap('#form_div'); += render :partial => "layouts/flash_msg" +- unless session[:host_items].nil? + %h3 + = n_("Host", "Hosts", session[:host_items].length) + = _('Selected') + = _('Click on a Host to fetch its settings') + %table.admittable{:height => '75'} + %tbody + %tr + %td + - if session[:host_items] + - @embedded = true + = render :partial => 'layouts/gtl' += react('HostEditForm', {:ids => (@host.id ? [@host.id] : session[:host_items])}) diff --git a/app/views/layouts/angular/_multi_auth_credentials.html.haml b/app/views/layouts/angular/_multi_auth_credentials.html.haml index 3437e990242..aba3ed4f310 100644 --- a/app/views/layouts/angular/_multi_auth_credentials.html.haml +++ b/app/views/layouts/angular/_multi_auth_credentials.html.haml @@ -40,13 +40,6 @@ %div{"ng-if" => "emsCommonModel.virtualization_selection == 'kubevirt'"} %i{"error-on-tab" => "kubevirt", :style => "color:#cc0000"} = _("Virtualization") - - elsif controller_name == "host" - = miq_tab_header('remote', nil, {'ng-click' => "changeAuthTab('remote')"}) do - = _("Remote Login") - = miq_tab_header('ws', nil, {'ng-click' => "changeAuthTab('ws')"}) do - = _("Web Services") - = miq_tab_header('ipmi', nil, {'ng-click' => "changeAuthTab('ipmi')"}) do - = _("IPMI") - elsif %w(ems_network).include?(controller_name) = miq_tab_header('amqp', nil, {'ng-click' => "changeAuthTab('amqp')"}) do %i{"error-on-tab" => "amqp", :style => "color:#cc0000"} @@ -446,50 +439,6 @@ :prefix => "kubevirt", :verify_title_off => _("kubevirt URL and API port fields are needed to perform validation."), :basic_info_needed => true} - - elsif controller_name == "host" - = miq_tab_content('remote', 'default') do - .form-group - .col-md-12 - = render :partial => "layouts/angular-bootstrap/auth_credentials_angular_bootstrap", - :locals => {:ng_show => true, - :ng_model => "#{ng_model}", - :validate_url => validate_url, - :id => record.id, - :prefix => "remote", - :basic_info_needed => true} - .form-group - .col-md-12 - %span{:style => "color:black"} - = _("Required if SSH login is disabled for the Default account.") - = miq_tab_content('ws', 'default') do - .form-group - .col-md-12 - = render :partial => "layouts/angular-bootstrap/auth_credentials_angular_bootstrap", - :locals => {:ng_show => true, - :ng_model => "#{ng_model}", - :validate_url => validate_url, - :id => record.id, - :prefix => "ws", - :basic_info_needed => true} - .form-group - .col-md-12 - %span{:style => "color:black"} - = _("Used for access to Web Services.") - = miq_tab_content('ipmi', 'default') do - .form-group - .col-md-12 - = render :partial => "layouts/angular-bootstrap/auth_credentials_angular_bootstrap", - :locals => {:ng_show => true, - :ng_model => "#{ng_model}", - :validate_url => validate_url, - :id => record.id, - :prefix => "ipmi", - :verify_title_off => _("IPMI IP Address, Username and matching password fields are needed to perform verification of credentials"), - :basic_info_needed => true} - .form-group - .col-md-12 - %span{:style => "color:black"} - = _("Used for access to IPMI.") %div{"ng-if" => "#{ng_model}.emstype == ''"} :javascript diff --git a/config/routes.rb b/config/routes.rb index 6122aa82b41..4ca79e29cd1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1757,7 +1757,6 @@ tagging_edit tl_chooser tree_autoload - update users wait_for_task ) + diff --git a/spec/controllers/host_controller_spec.rb b/spec/controllers/host_controller_spec.rb index d54195c21c1..06e175d372a 100644 --- a/spec/controllers/host_controller_spec.rb +++ b/spec/controllers/host_controller_spec.rb @@ -502,53 +502,6 @@ end end - describe '#update' do - before do - allow(controller).to receive(:assert_privileges) - allow(controller).to receive(:session).and_return(:host_items => nil) - end - - context 'canceling editing' do - before { controller.params = {:button => 'cancel', :id => h1.id} } - - it 'calls flash_and_redirect' do - expect(controller).to receive(:flash_and_redirect).with(_("Edit of Host \"%{name}\" was cancelled by the user") % {:name => h1.name}) - controller.send(:update) - end - - context 'selecting multiple Hosts' do - before { allow(controller).to receive(:session).and_return(:host_items => [h1.id, h2.id]) } - - it 'calls flash_and_redirect' do - expect(controller).to receive(:flash_and_redirect).with(_('Edit of credentials for selected Hosts was cancelled by the user')) - controller.send(:update) - end - end - end - - context 'saving changes' do - before do - allow(controller).to receive(:set_record_vars).and_return(true) - controller.instance_variable_set(:@breadcrumbs, []) - controller.params = {:button => 'save', :id => h1.id} - end - - it 'calls flash_and_redirect' do - expect(controller).to receive(:flash_and_redirect).with(_("Host \"%{name}\" was saved") % {:name => h1.name}) - controller.send(:update) - end - - context 'selecting multiple Hosts' do - before { allow(controller).to receive(:session).and_return(:host_items => [h1.id, h2.id]) } - - it 'calls flash_and_redirect' do - expect(controller).to receive(:flash_and_redirect).with(_('Credentials/Settings saved successfully')) - controller.send(:update) - end - end - end - end - describe '#report_data' do before do stub_user(:features => :all) diff --git a/spec/javascripts/controllers/host/host_form_controller_spec.js b/spec/javascripts/controllers/host/host_form_controller_spec.js deleted file mode 100644 index 006969bf154..00000000000 --- a/spec/javascripts/controllers/host/host_form_controller_spec.js +++ /dev/null @@ -1,182 +0,0 @@ -describe('hostFormController', function() { - var $scope, $controller, $httpBackend, miqService; - - beforeEach(module('ManageIQ')); - - beforeEach(inject(function(_$httpBackend_, $rootScope, _$controller_, _miqService_) { - miqService = _miqService_; - spyOn(miqService, 'showButtons'); - spyOn(miqService, 'hideButtons'); - spyOn(miqService, 'buildCalendar'); - spyOn(miqService, 'miqAjaxButton'); - spyOn(miqService, 'sparkleOn'); - spyOn(miqService, 'sparkleOff'); - $scope = $rootScope.$new(); - spyOn($scope, '$broadcast'); - $scope.hostModel = { name: 'name', ipaddress: 'ipaddress' }; - $scope.hostForm = { name: {}, - ipaddress: { - $dirty: false, - $name: 'ipaddress', - $setValidity: function (validationErrorKey, isValid){} - }, - $setPristine: function (){} - }; - - $scope.hostForm.$invalid = false; - $httpBackend = _$httpBackend_; - $httpBackend.whenGET('/host/host_form_fields/new').respond(); - $controller = _$controller_('hostFormController', { - $scope: $scope, - $attrs: {'formFieldsUrl': '/host/host_form_fields/', - 'updateUrl': '/host/update/'}, - hostFormId: 'new', - miqService: miqService - }); - })); - - afterEach(function() { - $httpBackend.verifyNoOutstandingExpectation(); - $httpBackend.verifyNoOutstandingRequest(); - }); - - describe('initialization', function() { - describe('when the hostFormId is an Id', function() { - var hostFormResponse = { - name: 'aaa', - hostname: '1.1.1.1', - custom_1: 'custom', - mac_address: '2.2.2.2', - ipmi_address: '3.3.3.3', - default_userid: 'abc', - remote_userid: 'xyz', - ws_userid: 'aaa', - ipmi_userid: 'zzz', - validate_id: '1', - }; - describe('when the filter type is all', function() { - beforeEach(inject(function(_$controller_) { - - $httpBackend.whenGET('/host/host_form_fields/12345').respond(hostFormResponse); - - $controller = _$controller_('hostFormController', {$scope: $scope, - $attrs: {'formFieldsUrl': '/host/host_form_fields/', - 'updateUrl': '/host/update/'}, - hostFormId: '12345'}); - $httpBackend.flush(); - })); - - it('sets the name to the value returned from the http request', function() { - expect($scope.hostModel.name).toEqual('aaa'); - }); - - it('sets the hostname to the value returned from the http request', function() { - expect($scope.hostModel.hostname).toEqual('1.1.1.1'); - }); - - it('sets the custom identifier to the value returned from the http request', function() { - expect($scope.hostModel.custom_1).toEqual('custom'); - }); - - it('sets the MAC address to the value returned from the http request', function() { - expect($scope.hostModel.mac_address).toEqual('2.2.2.2'); - }); - - it('sets the IPMI Address to the value returned from the http request', function() { - expect($scope.hostModel.ipmi_address).toEqual('3.3.3.3'); - }); - - it('sets the default password to the placeholder value if a default user exists', function() { - expect($scope.hostModel.default_password).toEqual(miqService.storedPasswordPlaceholder); - }); - - it('sets the remote password to the placeholder value if a remote user exists', function() { - expect($scope.hostModel.remote_password).toEqual(miqService.storedPasswordPlaceholder); - }); - - it('sets the ws password to the placeholder value if a ws user exists', function() { - expect($scope.hostModel.ws_password).toEqual(miqService.storedPasswordPlaceholder); - }); - - it('sets the ipmi password to the placeholder value if a ipmi user exists', function() { - expect($scope.hostModel.ipmi_password).toEqual(miqService.storedPasswordPlaceholder); - }); - - it('sets the validate id to the value returned from the http request', function() { - expect($scope.hostModel.validate_id).toEqual('1'); - }); - }); - }); - }); - - describe('#resetClicked', function() { - beforeEach(function() { - $scope.angularForm = { - $setPristine: function (value){}, - $setUntouched: function (value){}, - }; - $scope.resetClicked(); - }); - - it('does not turn the spinner on', function() { - expect(miqService.sparkleOn.calls.count()).toBe(0); - }); - - it("issues a broadcast for resetClicked event", function() { - expect($scope.$broadcast).toHaveBeenCalledWith('resetClicked'); - }); - }); - - describe('#saveClicked', function() { - beforeEach(function() { - $scope.angularForm = { - $setPristine: function (value){} - }; - $scope.saveClicked(); - }); - - it('turns the spinner on via the miqService', function() { - expect(miqService.sparkleOn).toHaveBeenCalled(); - }); - - it('turns the spinner on once', function() { - expect(miqService.sparkleOn.calls.count()).toBe(1); - }); - }); - - describe('Validates credential fields', function() { - beforeEach(inject(function($compile, miqService) { - var angularForm; - var element = angular.element( - '
' + - '' + - '' + - '' + - '
' - ); - - $compile(element)($scope); - $scope.$digest(); - angularForm = $scope.angularForm; - - $scope.angularForm.hostname.$setViewValue('abchost'); - $scope.angularForm.default_userid.$setViewValue('abcuser'); - $scope.angularForm.default_password.$setViewValue(miqService.storedPasswordPlaceholder); - })); - - it('returns true if all the Validation fields are filled in', function() { - $scope.angularForm.default_password.$setViewValue('abcpassword'); - expect($scope.canValidateBasicInfo()).toBe(true); - }); - - it('returns false if password fields are left blank', function() { - $scope.angularForm.default_password.$setViewValue(''); - expect($scope.canValidateBasicInfo()).toBe(false); - }); - - it('returns true if all the Validation fields are filled in and dirty', function() { - $scope.angularForm.default_password.$setViewValue('abc'); - expect($scope.canValidate()).toBe(true); - }); - }); -}); diff --git a/spec/routing/host_routing_spec.rb b/spec/routing/host_routing_spec.rb index 23f53c85ac0..17f5fa46650 100644 --- a/spec/routing/host_routing_spec.rb +++ b/spec/routing/host_routing_spec.rb @@ -10,7 +10,6 @@ it_behaves_like "A controller that has show list routes" it_behaves_like "A controller that has tagging routes" it_behaves_like "A controller that has timeline routes" - it_behaves_like "A controller that has CRUD routes" describe "#advanced_settings" do it "routes with GET" do @@ -227,4 +226,22 @@ expect(post("/host/users")).to route_to("host#users") end end + + describe "#index" do + it "routes with GET" do + expect(get("/#{controller_name}")).to route_to("#{controller_name}#index") + end + end + + describe "#edit" do + it "routes with GET" do + expect(get("/#{controller_name}/edit/123")).to route_to("#{controller_name}#edit", :id => "123") + end + end + + describe "#show" do + it "routes with GET" do + expect(get("/#{controller_name}/show/123")).to route_to("#{controller_name}#show", :id => "123") + end + end end diff --git a/spec/views/host/_form.html.haml_spec.rb b/spec/views/host/_form.html.haml_spec.rb deleted file mode 100644 index fcede389f40..00000000000 --- a/spec/views/host/_form.html.haml_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -describe "host/form" do - let(:partial) { |example| example.example_group.top_level_description } - - context "rendering fields in host new/edit form" do - before do - set_controller_for_view("host") - set_controller_for_view_to_be_nonrestful - allow(controller).to receive(:validate_before_save?).and_return(false) - @host = FactoryBot.create(:host) - @edit = {:new => @host} - end - - it "displays Host Name" do - render :partial => partial - expect(rendered).to match(/Hostname/) - end - - it "doesn't display IP Address" do - render :partial => partial - expect(rendered).not_to start_with('IP Address') - end - end -end