diff --git a/app/controllers/miq_ae_class_controller.rb b/app/controllers/miq_ae_class_controller.rb
index bb7da5ba57c..0dcf61e33a9 100644
--- a/app/controllers/miq_ae_class_controller.rb
+++ b/app/controllers/miq_ae_class_controller.rb
@@ -131,7 +131,6 @@ def set_right_cell_text(id, rec = nil)
txt = rec.domain? ? _('Automate Domain') : _('Automate Namespace')
@sb[:namespace_path] = rec.fqname
end
- @sb[:namespace_path]&.gsub!(%r{\/}, " / ")
@right_cell_text = "#{txt} #{_("\"%s\"") % get_rec_name(rec)}" unless %w[root aei aem].include?(nodes[0])
end
@@ -341,6 +340,9 @@ def replace_right_cell(options = {})
:serialize => @sb[:active_tab] == 'methods',
}
])
+ if @hide_bottom_bar
+ presenter.hide(:paging_div, :form_buttons_div)
+ end
else
# incase it was hidden for summary screen, and incase there were no records on show_list
presenter.hide(:paging_div, :form_buttons_div)
@@ -459,6 +461,7 @@ def edit_class
@ae_class = find_record_with_rbac(MiqAeClass, params[:id])
end
set_form_vars
+ @hide_bottom_bar = true
# have to get name and set node info, to load multiple tabs correctly
# rec_name = get_rec_name(@ae_class)
# get_node_info("aec-#{@ae_class.id}")
@@ -468,6 +471,24 @@ def edit_class
replace_right_cell
end
+ def edit_class_record
+ assert_privileges("miq_ae_class_edit")
+ unless params[:id]
+ obj = find_checked_items
+ @_params[:id] = obj[0]
+ end
+ @hide_bottom_bar = true
+
+ class_rec = MiqAeClass.find(params[:id])
+
+ render :json => {
+ :fqname => class_rec.fqname,
+ :name => class_rec.name,
+ :display_name => class_rec.display_name,
+ :description => class_rec.description
+ }
+ end
+
def edit_fields
assert_privileges("miq_ae_field_edit")
if params[:pressed] == "miq_ae_item_edit" # came from Namespace details screen
@@ -1291,6 +1312,7 @@ def new
assert_privileges("miq_ae_class_new")
@ae_class = MiqAeClass.new
set_form_vars
+ @hide_bottom_bar = true
@in_a_form = true
replace_right_cell
end
@@ -1841,8 +1863,50 @@ def ae_method_operations
end
end
+ def class_update
+ assert_privileges(params[:id].present? ? 'miq_ae_class_edit' : 'miq_ae_class_new')
+ @hide_bottom_bar = true
+ class_update_create
+ end
+
private
+ def class_update_create
+ case params[:button]
+ when "add", "save"
+ class_rec = params[:id].blank? ? MiqAeClass.new : MiqAeClass.find(params[:id]) # Get new or existing record
+ add_flash(_("Name is required"), :error) if params[:name].blank?
+ class_rec.name = params[:name]
+ class_rec.display_name = params[:display_name]
+ class_rec.description = params[:description]
+ class_rec.namespace_id = x_node.split('-')[1] if params[:id].blank?
+ begin
+ class_rec.save!
+ rescue StandardError
+ errors = []
+ class_rec.errors.each do |error|
+ errors.push("#{error.attribute.to_s.capitalize} #{error.message}")
+ end
+ @changed = true
+ render :json => {:error => errors, :status => 500}
+ else
+ edit_hash = {}
+ edit_hash[:new] = {:name => params[:name],
+ :display_name => params[:display_name], :description => params[:description]}
+ edit_hash[:current] = if params[:old_data]
+ {:name => params[:old_data][:name],
+ :display_name => params[:old_data][:display_name],
+ :description => params[:old_data][:description]}
+ else
+ {:name => nil, :display_name => nil, :description => nil}
+ end
+ AuditEvent.success(build_saved_audit(class_rec, edit_hash))
+ @edit = session[:edit] = nil # clean out the saved info
+ render :json => {:status => 200}
+ end
+ end
+ end
+
def get_template_class(location)
if location == "ansible_workflow_template"
ManageIQ::Providers::ExternalAutomationManager::ConfigurationWorkflow
diff --git a/app/javascript/components/miq-ae-class/class-form.schema.js b/app/javascript/components/miq-ae-class/class-form.schema.js
new file mode 100644
index 00000000000..dda2cd26785
--- /dev/null
+++ b/app/javascript/components/miq-ae-class/class-form.schema.js
@@ -0,0 +1,38 @@
+import { componentTypes } from '@@ddf';
+
+const createSchema = (fqname) => ({
+ fields: [
+ {
+ component: componentTypes.TEXT_FIELD,
+ name: 'fqname',
+ label: 'Fully Qualified Name',
+ value: `${fqname}`,
+ disabled: true,
+ },
+ {
+ component: componentTypes.TEXT_FIELD,
+ id: 'name',
+ name: 'name',
+ label: __('Name'),
+ maxLength: 128,
+ validate: [{ type: 'customValidatorForNameField' }],
+ isRequired: true,
+ },
+ {
+ component: componentTypes.TEXT_FIELD,
+ id: 'display_name',
+ name: 'display_name',
+ label: __('Display Name'),
+ maxLength: 128,
+ },
+ {
+ component: componentTypes.TEXT_FIELD,
+ id: 'description',
+ name: 'description',
+ label: __('Description'),
+ maxLength: 255,
+ },
+ ],
+});
+
+export default createSchema;
diff --git a/app/javascript/components/miq-ae-class/index.jsx b/app/javascript/components/miq-ae-class/index.jsx
new file mode 100644
index 00000000000..7630e4aba08
--- /dev/null
+++ b/app/javascript/components/miq-ae-class/index.jsx
@@ -0,0 +1,193 @@
+import React, { useState, useEffect } from 'react';
+import { FormSpy } from '@data-driven-forms/react-form-renderer';
+import { Button } from 'carbon-components-react';
+import MiqFormRenderer, { useFormApi } from '@@ddf';
+import PropTypes from 'prop-types';
+import createSchema from './class-form.schema';
+import miqRedirectBack from '../../helpers/miq-redirect-back';
+import miqFlash from '../../helpers/miq-flash';
+
+const MiqAeClass = ({ classRecord, fqname }) => {
+ const formattedFqname = fqname.replace(/\s+/g, '');
+ const [data, setData] = useState({
+ isLoading: true,
+ initialValues: undefined,
+ });
+
+ const isEdit = !!(classRecord && classRecord.id);
+
+ useEffect(() => {
+ if (isEdit) {
+ http.get(`/miq_ae_class/edit_class_record/${classRecord.id}/`).then((recordValues) => {
+ if (recordValues) {
+ setData({ ...data, isLoading: false, initialValues: recordValues });
+ }
+ });
+ } else {
+ const initialValues = {
+ formattedFqname,
+ name: classRecord && classRecord.name,
+ display_name: classRecord && classRecord.display_name,
+ description: classRecord && classRecord.description,
+ };
+ setData({ ...data, isLoading: false, initialValues });
+ }
+ }, [classRecord]);
+
+ const onSubmit = (values) => {
+ miqSparkleOn();
+
+ const params = {
+ action: isEdit ? 'edit' : 'create',
+ name: values.name,
+ display_name: values.display_name,
+ description: values.description,
+ old_data: data.initialValues,
+ button: classRecord.id ? 'save' : 'add',
+ };
+
+ const request = isEdit
+ ? http.post(`/miq_ae_class/class_update/${classRecord.id}`, params)
+ : http.post(`/miq_ae_class/class_update/`, params);
+
+ request
+ .then((response) => {
+ if (response.status === 200) {
+ const confirmation = isEdit ? __(`Class "%s" was saved`) : __(`Class "%s" was added`);
+ const message = sprintf(confirmation, values.name);
+ miqRedirectBack(message, 'success', '/miq_ae_class/explorer');
+ } else {
+ miqSparkleOff();
+ miqFlash('error', response.error);
+ }
+ })
+ .catch(miqSparkleOff);
+ };
+
+ const onCancel = () => {
+ const confirmation = classRecord.id ? __(`Edit of Class "%s" cancelled by the user`)
+ : __(`Add of new Class was cancelled by the user`);
+ const message = sprintf(confirmation, classRecord.name);
+ miqRedirectBack(message, 'warning', '/miq_ae_class/explorer');
+ };
+
+ const customValidatorMapper = {
+ customValidatorForNameField: () => (value) => {
+ if (!value) {
+ return __('Required');
+ }
+ if (!value.match('^[a-zA-Z0-9_.-]*$')) {
+ return __('Name may contain only alphanumeric and _ . - characters');
+ }
+ return false;
+ },
+ };
+
+ return (!data.isLoading
+ ? (
+
+ {}}
+ FormTemplate={(props) => }
+ />
+
+ ) : null
+ );
+};
+
+const FormTemplate = ({
+ formFields, recId,
+}) => {
+ const {
+ handleSubmit, onReset, onCancel, getState,
+ } = useFormApi();
+ const { valid, pristine } = getState();
+ const submitLabel = !!recId ? __('Save') : __('Add');
+ return (
+
+ );
+};
+
+MiqAeClass.propTypes = {
+ classRecord: PropTypes.shape({
+ id: PropTypes.number,
+ name: PropTypes.string,
+ display_name: PropTypes.string,
+ description: PropTypes.string,
+ }),
+ fqname: PropTypes.string.isRequired,
+};
+
+MiqAeClass.defaultProps = {
+ classRecord: undefined,
+};
+
+FormTemplate.propTypes = {
+ formFields: PropTypes.arrayOf(
+ PropTypes.shape({ id: PropTypes.number }),
+ PropTypes.shape({ name: PropTypes.string }),
+ PropTypes.shape({ display_name: PropTypes.string }),
+ PropTypes.shape({ description: PropTypes.string }),
+ ),
+ recId: PropTypes.number,
+};
+
+FormTemplate.defaultProps = {
+ formFields: undefined,
+ recId: undefined,
+};
+
+export default MiqAeClass;
diff --git a/app/javascript/packs/component-definitions-common.js b/app/javascript/packs/component-definitions-common.js
index f56709a0b77..b5e3b5de67f 100644
--- a/app/javascript/packs/component-definitions-common.js
+++ b/app/javascript/packs/component-definitions-common.js
@@ -177,6 +177,7 @@ import WorkflowPayload from '../components/workflows/workflow_payload';
import WorkflowRepositoryForm from '../components/workflow-repository-form';
import XmlHolder from '../components/XmlHolder';
import ZoneForm from '../components/zone-form';
+import MiqAeClass from '../components/miq-ae-class';
/**
* Add component definitions to this file.
@@ -363,3 +364,4 @@ ManageIQ.component.addReact('WorkflowPayload', WorkflowPayload);
ManageIQ.component.addReact('WorkflowRepositoryForm', WorkflowRepositoryForm);
ManageIQ.component.addReact('XmlHolder', XmlHolder);
ManageIQ.component.addReact('ZoneForm', ZoneForm);
+ManageIQ.component.addReact('MiqAeClass', MiqAeClass);
diff --git a/app/javascript/spec/miq-ae-class-form/__snapshots__/miq-ae-class-form.spec.js.snap b/app/javascript/spec/miq-ae-class-form/__snapshots__/miq-ae-class-form.spec.js.snap
new file mode 100644
index 00000000000..412578bfe67
--- /dev/null
+++ b/app/javascript/spec/miq-ae-class-form/__snapshots__/miq-ae-class-form.spec.js.snap
@@ -0,0 +1,5 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`MiqAeClass Form Component should render add class form correctly 1`] = `""`;
+
+exports[`MiqAeClass Form Component should render edit class form correctly 1`] = `""`;
diff --git a/app/javascript/spec/miq-ae-class-form/miq-ae-class-form.spec.js b/app/javascript/spec/miq-ae-class-form/miq-ae-class-form.spec.js
new file mode 100644
index 00000000000..8e369f9327b
--- /dev/null
+++ b/app/javascript/spec/miq-ae-class-form/miq-ae-class-form.spec.js
@@ -0,0 +1,62 @@
+import React from 'react';
+import fetchMock from 'fetch-mock';
+import { shallow } from 'enzyme';
+import toJson from 'enzyme-to-json';
+import MiqAeClass from '../../components/miq-ae-class';
+
+describe('MiqAeClass Form Component', () => {
+ const classMockData = [
+ {
+ href: `/miq_ae_class/edit_class/2/`,
+ id: 2,
+ description: 'Configured System Provision',
+ },
+ ];
+
+ const MiqAeClassEditData = {
+ id: 40,
+ name: 'test',
+ display_name: 'test display name',
+ description: 'test description',
+ };
+
+ const fqName = 'Sample FQ Name';
+
+ afterEach(() => {
+ fetchMock.reset();
+ fetchMock.restore();
+ });
+
+ it('should render add class form correctly', async() => {
+ const wrapper = shallow();
+
+ fetchMock.get(`/miq_ae_class/new?&expand=resources/`, classMockData);
+
+ await new Promise((resolve) => {
+ setImmediate(() => {
+ wrapper.update();
+ expect(toJson(wrapper)).toMatchSnapshot();
+ resolve();
+ });
+ });
+ });
+
+ it('should render edit class form correctly', async() => {
+ const wrapper = shallow();
+
+ fetchMock.get(`/miq_ae_class/edit_class_react/${MiqAeClassEditData.id}?&expand=resources/`, classMockData);
+ await new Promise((resolve) => {
+ setImmediate(() => {
+ wrapper.update();
+ expect(toJson(wrapper)).toMatchSnapshot();
+ resolve();
+ });
+ });
+ });
+});
diff --git a/app/views/miq_ae_class/_class_form.html.haml b/app/views/miq_ae_class/_class_form.html.haml
index 5d3d9bc3808..a3e5041d203 100644
--- a/app/views/miq_ae_class/_class_form.html.haml
+++ b/app/views/miq_ae_class/_class_form.html.haml
@@ -1,35 +1,6 @@
-- url = url_for_only_path(:action => 'form_field_changed', :id => (@ae_class.id || 'new'))
-- obs = {:interval => '.5', :url => url}.to_json
-%h3
- = _('Properties')
-.form-horizontal
- .form-group
- %label.col-md-2.control-label
- = _('Fully Qualified Name')
- .col-md-8
- = @sb[:namespace_path]
- .form-group
- %label.col-md-2.control-label
- = _('Name')
- .col-md-8
- = text_field_tag("name", @edit[:new][:name],
- :maxlength => ViewHelper::MAX_NAME_LEN,
- :class => "form-control",
- "data-miq_observe" => obs)
- = javascript_tag(javascript_focus('name'))
- .form-group
- %label.col-md-2.control-label
- = _('Display Name')
- .col-md-8
- = text_field_tag("display_name", @edit[:new][:display_name],
- :maxlength => ViewHelper::MAX_NAME_LEN,
- :class => "form-control",
- "data-miq_observe" => obs)
- .form-group
- %label.col-md-2.control-label
- = _('Description')
- .col-md-8
- = text_field_tag("description", @edit[:new][:description],
- :maxlength => ViewHelper::MAX_NAME_LEN,
- :class => "form-control",
- "data-miq_observe" => obs)
+- if @ae_class
+ - if @in_a_form
+ = react('MiqAeClass',
+ :classRecord => @ae_class,
+ :fqname => @sb[:namespace_path])
+
diff --git a/config/routes.rb b/config/routes.rb
index 2056e137c7d..a796ad5f217 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1911,6 +1911,7 @@
ae_methods
ae_method_operations
show
+ edit_class_record
],
:post => %w[
add_update_method
@@ -1955,6 +1956,7 @@
x_button
x_history
x_show
+ class_update
] + adv_search_post +
exp_post
},
diff --git a/cypress/e2e/ui/Automation/Embedded-Automate/Explorer/class.cy.js b/cypress/e2e/ui/Automation/Embedded-Automate/Explorer/class.cy.js
new file mode 100644
index 00000000000..ecdc1ed6f0a
--- /dev/null
+++ b/cypress/e2e/ui/Automation/Embedded-Automate/Explorer/class.cy.js
@@ -0,0 +1,281 @@
+/* eslint-disable no-undef */
+
+describe('Automation > Embedded Automate > Explorer', () => {
+ before(() => {
+ // Create a Domain and Namespace before all the tests
+ cy.login();
+ cy.intercept('POST', '/ops/accordion_select?id=rbac_accord').as('accordion');
+ cy.menu('Automation', 'Embedded Automate', 'Explorer');
+ cy.get('#explorer_title_text');
+
+ // Creates a Domain
+ cy.get('[title="Datastore"]').click();
+ cy.get('[title="Configuration"]').click();
+ cy.get('[title="Add a New Domain"]').click();
+ cy.get('[name="name"]').type('TestDomain', {force: true});
+ cy.get('[name="description"]').type('This is a test domain');
+ cy.get('#enabled').check();
+ cy.get('[class="bx--btn bx--btn--primary"]').contains('Add').click();
+
+ // Check for the success message
+ cy.get('div.alert.alert-success.alert-dismissable').should('exist').and('contain', 'Automate Domain "TestDomain" was added').find('button.close').should('exist');
+
+ // Creates a Namespace
+ cy.get('[title="Datastore"]').click();
+ cy.get('[title="Automate Domain: TestDomain"]').click(); // Click on Domain
+ cy.get('[title="Configuration"]').click();
+ cy.get('[title="Add a New Namespace"]').click();
+ cy.get('[name="name"]').type('TestNameSpace', {force: true});
+ cy.get('[name="description"]').type('This is a test NameSpace');
+ cy.get('.bx--btn--primary').contains('Add').click();
+
+ cy.wait(1000); // Need this wait or else namespace doesn't get added properly
+ });
+
+ beforeEach(() => {
+ cy.login();
+ cy.intercept('POST', '/ops/accordion_select?id=rbac_accord').as('accordion');
+ cy.menu('Automation', 'Embedded Automate', 'Explorer');
+ cy.get('#explorer_title_text');
+ });
+
+ describe('Class Form', () => {
+ it('Cancel button works on the form', () => {
+ // Clicks the Cancel button on the create class form
+ cy.get('[title="Datastore"]').click();
+ cy.get('[title="Automate Domain: TestDomain"]').click();
+ cy.get('[title="Automate Namespace: TestNameSpace"]').click();
+ cy.get('[title="Configuration"]').click();
+ cy.get('[title="Add a New Class"]').click();
+ cy.get('[class="bx--btn bx--btn--secondary"]').contains('Cancel').click(); // clicks Cancel button
+
+ // Make sure that the cancel button redirects back to the namespace page
+ cy.get('[id="explorer_title_text"]').contains('Automate Namespace "TestNameSpace"');
+ cy.get('.flash_text_div > .alert').contains('Add of new Class was cancelled by the user');
+ cy.get('#ns_details_div > .alert').contains('The selected Namespace is empty');
+ });
+
+ it('Reset button works on the form', () => {
+ // Creates a Class
+ cy.get('[title="Datastore"]').click();
+ cy.get('[title="Automate Domain: TestDomain"]').click(); // clicks on Domain
+ cy.get('[title="Automate Namespace: TestNameSpace"]').click(); // clicks on Namespace
+ cy.get('[title="Configuration"]').click();
+ cy.get('[title="Add a New Class"]').click();
+ cy.get('[name="name"]').type('TestClass', {force: true});
+ cy.get('[name="display_name"]').type('Test Class 0');
+ cy.get('[name="description"').type('This is a test class description');
+ cy.get('.bx--btn--primary').contains('Add').click();
+
+ // Check for the success message
+ cy.get('#flash_msg_div .alert.alert-success').should('exist').and('be.visible').and('contain', 'Class "TestClass" was added');
+
+ // Navigate to the Properties tab of the class just created
+ cy.get('.clickable-row').contains('Test Class 0').click();
+ cy.get('#props_tab > a').click();
+
+ // Edits the class
+ cy.get('[title="Configuration"]').click();
+ cy.get('[title="Edit this Class"]').click();
+ cy.get('[name="name"]').type('Edit', {force: true});
+ cy.get('[name="display_name"]').clear().type('Edited Test Class');
+ cy.get('[name="description"').clear().type('Edited test class description');
+
+ // Click Reset Button
+ cy.get('.btnRight.bx--btn--secondary').contains('Reset').click();
+
+ // Verify that the form was reset
+ cy.get('.flash_text_div > .alert').contains('All changes have been reset');
+ cy.get('[name="name"]').should('have.value', 'TestClass');
+ cy.get('[name="display_name"]').should('have.value', 'Test Class 0');
+ cy.get('[name="description"]').should('have.value', 'This is a test class description');
+
+ // Click Cancel button
+ cy.get('[class="bx--btn bx--btn--secondary"]').contains('Cancel').click();
+
+ // Removes class
+ cy.get('[title="Configuration"]').click();
+ cy.get('[title="Remove this Class"]').click();
+
+ // Verify that the class was removed
+ cy.get('div.alert.alert-success.alert-dismissable').should('exist').and('contain', 'Automate Class "TestClass": Delete successful');
+ cy.get('#ns_details_div > .alert').contains('The selected Namespace is empty');
+ });
+
+ it('Form validation works', () => {
+ // Try to create a Class with an invalid name
+ cy.get('[title="Datastore"]').click();
+ cy.get('[title="Automate Domain: TestDomain"]').click(); // clicks on Domain
+ cy.get('[title="Automate Namespace: TestNameSpace"]').click(); // clicks on Namespace
+
+ cy.get('[title="Configuration"]').click();
+ cy.get('[title="Add a New Class"]').click();
+ cy.get('[name="name"]').type('Test Class', {force: true});
+ cy.get('[name="display_name"]').type('Test Class 0');
+ cy.get('[name="description"').type('This is a test class description');
+
+ // Verify that error message appears
+ cy.get('#name-error-msg').contains('Name may contain only alphanumeric and _ . - characters');
+ cy.get('.btnRight').should('be.disabled');
+
+ // Enter a valid name
+ cy.get('[name="name"]').clear({force: true}).type('TestClass');
+ cy.get('.btnRight').click();
+
+ cy.get('#flash_msg_div .alert.alert-success').should('exist').and('be.visible').and('contain', 'Class "TestClass" was added');
+
+ // Navigate to the Properties tab of the class just created
+ cy.get('.clickable-row').contains('Test Class 0').click();
+ cy.get('#props_tab > a').click();
+
+ // Verify that the class created correctly
+ cy.get(':nth-child(1) > .label_header').contains('Fully Qualified Name');
+ cy.get(':nth-child(1) > .content_value').contains('/ TestDomain / TestNameSpace / TestClass');
+ cy.get(':nth-child(2) > .label_header').contains('Name');
+ cy.get(':nth-child(2) > .content_value').contains('TestClass');
+ cy.get(':nth-child(3) > .label_header').contains('Display Name');
+ cy.get(':nth-child(3) > .content_value').contains('Test Class 0');
+ cy.get(':nth-child(4) > .label_header').contains('Description');
+ cy.get(':nth-child(4) > .content_value').contains('This is a test class description');
+ cy.get(':nth-child(5) > .label_header').contains('Instances');
+ cy.get(':nth-child(5) > .content_value').contains('0');
+
+ cy.get('.list-group-item').then((listItems) => {
+ const nums = [...Array(listItems.length).keys()];
+ nums.forEach((index) => {
+ if (listItems[index].innerText === 'TestNameSpace') {
+ cy.get(listItems[index]).click();
+ }
+ });
+ });
+
+ // Try to create a Class with the same name
+ cy.get('[title="Configuration"]').click();
+ cy.get('[title="Add a New Class"]').click();
+ cy.get('[name="name"]').type('TestClass', {force: true});
+ cy.get('[name="display_name"]').type('Test Class 1');
+ cy.get('[name="description"').type('This is a test class description');
+ cy.get('.btnRight').click();
+
+ // Verify that error message appears
+ cy.get('.alert').contains('Name has already been taken');
+
+ // Enter a new name
+ cy.get('[name="name"]').clear({force: true}).type('NewTestClass', {force: true});
+ cy.get('.btnRight').click();
+
+ // Check for the success message
+ cy.get('#flash_msg_div .alert.alert-success').should('exist').and('be.visible').and('contain', 'Class "NewTestClass" was added');
+
+ // Navigate to the Properties tab of the class just created
+ cy.get('.clickable-row').contains('Test Class 1').click();
+ cy.get('#props_tab > a').click();
+
+ // Verify that the class created correctly
+ cy.get(':nth-child(1) > .label_header').contains('Fully Qualified Name');
+ cy.get(':nth-child(1) > .content_value').contains('/ TestDomain / TestNameSpace / NewTestClass');
+ cy.get(':nth-child(2) > .label_header').contains('Name');
+ cy.get(':nth-child(2) > .content_value').contains('NewTestClass');
+ cy.get(':nth-child(3) > .label_header').contains('Display Name');
+ cy.get(':nth-child(3) > .content_value').contains('Test Class 1');
+ cy.get(':nth-child(4) > .label_header').contains('Description');
+ cy.get(':nth-child(4) > .content_value').contains('This is a test class description');
+ cy.get(':nth-child(5) > .label_header').contains('Instances');
+ cy.get(':nth-child(5) > .content_value').contains('0');
+
+ // Removes class
+ cy.get('[title="Configuration"]').click();
+ cy.get('[title="Remove this Class"]').click();
+
+ // Verify that the class was removed
+ cy.get('div.alert.alert-success.alert-dismissable').should('exist').and('contain', 'Automate Class "NewTestClass": Delete successful');
+
+ // Navigate to the Properties tab of the first class just created
+ cy.get('.clickable-row').contains('Test Class 0').click();
+
+ // Removes class
+ cy.get('[title="Configuration"]').click();
+ cy.get('[title="Remove this Class"]').click();
+
+ // Verify that the class was removed
+ cy.get('div.alert.alert-success.alert-dismissable').should('exist').and('contain', 'Automate Class "TestClass": Delete successful');
+ cy.get('#ns_details_div > .alert').contains('The selected Namespace is empty');
+ });
+
+ it('Creates and edits an automate class', () => {
+ // Creates a Class
+ cy.get('[title="Datastore"]').click({force: true});
+ cy.get('[title="Automate Domain: TestDomain"]').click({force: true}); // clicks on Domain
+ cy.get('[title="Automate Namespace: TestNameSpace"]').click({force: true}); // clicks on Namespace
+ cy.get('[title="Configuration"]').click({force: true});
+ cy.get('[title="Add a New Class"]').click({force: true});
+ cy.get('[name="name"]').type('TestClass', {force: true});
+ cy.get('[name="display_name"]').type('Test Class 0');
+ cy.get('[name="description"').type('This is a test class description');
+ cy.get('.bx--btn--primary').contains('Add').click();
+
+ // Check for the success message
+ cy.get('#flash_msg_div .alert.alert-success').should('exist').and('be.visible').and('contain', 'Class "TestClass" was added');
+
+ // Navigate to the Properties tab of the class just created
+ cy.get('.clickable-row').contains('Test Class 0').click();
+ cy.get('#props_tab > a').click();
+
+ // Verify that the class created correctly
+ cy.get(':nth-child(1) > .label_header').contains('Fully Qualified Name');
+ cy.get(':nth-child(1) > .content_value').contains('/ TestDomain / TestNameSpace / TestClass');
+ cy.get(':nth-child(2) > .label_header').contains('Name');
+ cy.get(':nth-child(2) > .content_value').contains('TestClass');
+ cy.get(':nth-child(3) > .label_header').contains('Display Name');
+ cy.get(':nth-child(3) > .content_value').contains('Test Class 0');
+ cy.get(':nth-child(4) > .label_header').contains('Description');
+ cy.get(':nth-child(4) > .content_value').contains('This is a test class description');
+ cy.get(':nth-child(5) > .label_header').contains('Instances');
+ cy.get(':nth-child(5) > .content_value').contains('0');
+
+ // Edits the class
+ cy.get('[title="Configuration"]').click({force: true});
+ cy.get('[title="Edit this Class"]').click({force: true});
+ cy.get('[name="name"]').type('Edit', {force: true});
+ cy.get('[name="display_name"]').clear().type('Edited Test Class', {force: true});
+ cy.get('[name="description"').clear().type('Edited test class description');
+ cy.get('[class="btnRight bx--btn bx--btn--primary"]').contains('Save').click({force: true});
+
+ // Navigate to the Properties tab of the class just edited
+ cy.get('#props_tab a').click();
+
+ // Verify that the class edited correctly
+ cy.get(':nth-child(1) > .label_header').contains('Fully Qualified Name');
+ cy.get(':nth-child(1) > .content_value').contains('/ TestDomain / TestNameSpace / TestClassEdit');
+ cy.get(':nth-child(2) > .label_header').contains('Name');
+ cy.get(':nth-child(2) > .content_value').contains('TestClassEdit');
+ cy.get(':nth-child(3) > .label_header').contains('Display Name');
+ cy.get(':nth-child(3) > .content_value').contains('Edited Test Class');
+ cy.get(':nth-child(4) > .label_header').contains('Description');
+ cy.get(':nth-child(4) > .content_value').contains('Edited test class description');
+ cy.get(':nth-child(5) > .label_header').contains('Instances');
+ cy.get(':nth-child(5) > .content_value').contains('0');
+
+ // Removes class
+ cy.get('[title="Configuration"]').click({force: true});
+ cy.get('[title="Remove this Class"]').click({force: true});
+
+ // Verify that the class was removed
+ cy.get('div.alert.alert-success.alert-dismissable')
+ .should('exist')
+ .and('contain', 'Automate Class "TestClassEdit": Delete successful');
+ cy.get('#ns_details_div > .alert').contains('The selected Namespace is empty');
+ });
+ });
+
+ after(() => {
+ // Remove the Domain after all the tests
+ cy.menu('Automation', 'Embedded Automate', 'Explorer');
+ cy.get('[title="Datastore"]').click({force: true});
+ cy.get('[title="Automate Domain: TestDomain"]').click({force: true});
+ cy.get('[title="Configuration"]').click({force: true});
+ cy.get('[title="Remove this Domain"]').click({force: true});
+
+ cy.get('.bx--data-table-content tbody tr').should('not.contain', 'Automate Domain: TestDomain');
+ });
+});
diff --git a/spec/controllers/miq_ae_class_controller_spec.rb b/spec/controllers/miq_ae_class_controller_spec.rb
index 954d325cbee..27964a84ac4 100644
--- a/spec/controllers/miq_ae_class_controller_spec.rb
+++ b/spec/controllers/miq_ae_class_controller_spec.rb
@@ -24,7 +24,7 @@
id = "aec-#{cls.id}"
fq_name = cls.fqname
controller.send(:set_right_cell_text, id, cls)
- expect(assigns(:sb)[:namespace_path]).to eq(fq_name.gsub!(%r{\/}, " / "))
+ expect(assigns(:sb)[:namespace_path]).to eq(fq_name)
id = "root"
fq_name = ""
@@ -291,7 +291,7 @@
:active_tree => :ae_tree,
:trees => {:ae_tree => {:active_node => node}})
controller.send(:get_node_info, node)
- expect(assigns(:sb)[:namespace_path]).to eq(ns1.fqname.gsub!(%r{\/}, " / "))
+ expect(assigns(:sb)[:namespace_path]).to eq(ns1.fqname)
end
end