diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c825f4cd230..4898ac9ef7e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,8 +12,8 @@ jobs: strategy: matrix: ruby-version: - - '3.0' - '3.1' + - '3.3' node-version: - 18 test-suite: @@ -63,6 +63,6 @@ jobs: - name: Run tests run: bundle exec rake - name: Report code coverage - if: ${{ github.ref == 'refs/heads/master' && matrix.ruby-version == '3.1' && matrix.test-suite == 'spec' }} + if: ${{ github.ref == 'refs/heads/master' && matrix.ruby-version == '3.3' && matrix.test-suite == 'spec' }} continue-on-error: true uses: paambaati/codeclimate-action@v5 diff --git a/app/controllers/application_controller/automate.rb b/app/controllers/application_controller/automate.rb index e2fbf551043..ec8d1d38c00 100644 --- a/app/controllers/application_controller/automate.rb +++ b/app/controllers/application_controller/automate.rb @@ -33,36 +33,6 @@ def resolve_button_throw end private :resolve_button_throw - # Copy current URI as an automate button - def resolve_button_copy - session[:resolve_object] = copy_hash(@resolve) - head :ok - end - private :resolve_button_copy - - # Copy current URI as an automate button - def resolve_button_paste - @resolve = copy_hash(session[:resolve_object]) - @edit = session[:edit] - @custom_button = @edit[:custom_button] - @edit[:instance_names] = @resolve[:instance_names] - @edit[:new][:instance_name] = @resolve[:new][:instance_name] - @edit[:new][:object_message] = @resolve[:new][:object_message] - @edit[:new][:object_request] = @resolve[:new][:object_request] - @edit[:new][:attrs] = @resolve[:new][:attrs] - @edit[:new][:target_class] = @resolve[:target_class] = @resolve[:new][:target_class] - @edit[:uri] = @resolve[:uri] - (ApplicationController::AE_MAX_RESOLUTION_FIELDS - @resolve[:new][:attrs].length).times { @edit[:new][:attrs].push([]) } - @changed = (@edit[:new] != @edit[:current]) - render :update do |page| - page << javascript_prologue - page.replace_html("main_div", :partial => "shared/buttons/ab_list") - page << javascript_for_miq_button_visibility_changed(@changed) - page << "miqSparkle(false);" - end - end - private :resolve_button_paste - # Copy current URI as an automate button def resolve_button_simulate @edit = copy_hash(session[:resolve]) @@ -126,8 +96,6 @@ def resolve case params[:button] when "throw", "retry" then resolve_button_throw - when "copy" then resolve_button_copy - when "paste" then resolve_button_paste when "simulate" then resolve_button_simulate else resolve_button_reset_or_none end diff --git a/app/helpers/application_helper/button/ae_copy_simulate.rb b/app/helpers/application_helper/button/ae_copy_simulate.rb deleted file mode 100644 index 5737bb7d8e6..00000000000 --- a/app/helpers/application_helper/button/ae_copy_simulate.rb +++ /dev/null @@ -1,8 +0,0 @@ -class ApplicationHelper::Button::AeCopySimulate < ApplicationHelper::Button::ButtonWithoutRbacCheck - def disabled? - if @resolve[:button_class].blank? - @error_message = _('Object attribute must be specified to copy object details for use in a Button') - @error_message.present? - end - end -end diff --git a/app/helpers/application_helper/toolbar/miq_ae_tools_simulate_center.rb b/app/helpers/application_helper/toolbar/miq_ae_tools_simulate_center.rb index d3c6a8c4a9b..74df5aec9bf 100644 --- a/app/helpers/application_helper/toolbar/miq_ae_tools_simulate_center.rb +++ b/app/helpers/application_helper/toolbar/miq_ae_tools_simulate_center.rb @@ -1,12 +1,2 @@ class ApplicationHelper::Toolbar::MiqAeToolsSimulateCenter < ApplicationHelper::Toolbar::Basic - button_group('miq_ae_tools_vmdb', [ - button( - :ae_copy_simulate, - 'fa fa-files-o fa-lg', - N_('Copy object details for use in a Button'), - N_('Copy'), - :url => "resolve", - :url_parms => "?button=copy", - :klass => ApplicationHelper::Button::AeCopySimulate), - ]) end diff --git a/app/javascript/components/ansible-playbook-edit-catalog-form/helper.js b/app/javascript/components/ansible-playbook-edit-catalog-form/helper.js index e884bbe03ca..ff2b942633b 100644 --- a/app/javascript/components/ansible-playbook-edit-catalog-form/helper.js +++ b/app/javascript/components/ansible-playbook-edit-catalog-form/helper.js @@ -182,7 +182,8 @@ export const KeyValueListComponent = (props) => {
{input.value && input.value.map((pair, index) => ( -
+ // eslint-disable-next-line react/no-array-index-key +
{headerLabel(header.header)} @@ -148,15 +150,17 @@ const MiqDataTable = ({ stickyHeader={stickyHeader} > {({ - rows, getTableProps, getHeaderProps, getRowProps, getSelectionProps, + rows, headers, getTableProps, getHeaderProps, getRowProps, getSelectionProps, }) => ( - - - {rowCheckBox && selectAll(getSelectionProps)} - {renderHeaders(getHeaderProps)} - - + {headers[0] && headers[0].header !== '' ? ( + + + {rowCheckBox && selectAll(getSelectionProps)} + {renderHeaders(getHeaderProps)} + + + ) : undefined} {sortableRows(rows).map((row, index) => { const item = propRows[index]; diff --git a/app/javascript/components/settings-details-tab/index.jsx b/app/javascript/components/settings-details-tab/index.jsx new file mode 100644 index 00000000000..37d895f0443 --- /dev/null +++ b/app/javascript/components/settings-details-tab/index.jsx @@ -0,0 +1,72 @@ +import React, { useState } from 'react'; +import MiqDataTable from '../miq-data-table'; +import RegionForm from '../region-form'; + +const settingsDetailsTab = ({ + region, scanItemsCount, zonesCount, miqSchedulesCount, +}) => { + const [rows, setRows] = useState([ + { + id: '0', + region: { + text: `${region.description} [${region.region}]`, + icon: 'carbon--Db2Database', + }, + treeBox: 'settings_tree', + }, + { + id: '1', + region: { + text: `${__('Analysis Profiles')} (${scanItemsCount})`, + icon: 'carbon--Search', + }, + treeBox: 'settings_tree', + nodeKey: 'xx-sis', + }, + { + id: '2', + region: { + text: `${__('Zones')} (${zonesCount})`, + icon: 'carbon--CirclePacking', + }, + treeBox: 'settings_tree', + nodeKey: 'xx-z', + }, + { + id: '3', + region: { + text: `${__('Schedules')} (${miqSchedulesCount})`, + icon: 'carbon--Time', + }, + treeBox: 'settings_tree', + nodeKey: 'xx-msc', + }, + ]); + + const headers = [ + { + key: 'region', + header: '', + }, + ]; + + const onSelect = (selectedRow) => { + const selected = rows.find((row) => row.id === selectedRow.id); + if (selected.nodeKey) { + miqTreeActivateNode(selected.treeBox, selected.nodeKey); + } else { + setRows((prevRows) => prevRows.filter((row) => row.id !== '0')); + } + }; + + return ( +
+ {rows.find((row) => row.id === '0') === undefined && ( + + )} + onSelect(selectedRow)} /> +
+ ); +}; + +export default settingsDetailsTab; diff --git a/app/javascript/components/terraform-template-catalog-form/helper.js b/app/javascript/components/terraform-template-catalog-form/helper.js index 04e37f9b58b..fe785f432f4 100644 --- a/app/javascript/components/terraform-template-catalog-form/helper.js +++ b/app/javascript/components/terraform-template-catalog-form/helper.js @@ -183,7 +183,8 @@ export const KeyValueListComponent = (props) => {
{input.value && input.value.map((pair, index) => ( -
+ // eslint-disable-next-line react/no-array-index-key +
+`; diff --git a/app/javascript/spec/settings-details-tab/settings-details-tab.spec.js b/app/javascript/spec/settings-details-tab/settings-details-tab.spec.js new file mode 100644 index 00000000000..a9771d0be75 --- /dev/null +++ b/app/javascript/spec/settings-details-tab/settings-details-tab.spec.js @@ -0,0 +1,40 @@ +import React from 'react'; +import fetchMock from 'fetch-mock'; +import { shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import settingsDetailsTab from '../../components/settings-details-tab'; + +describe('SettingsDetailsTab Component', () => { + const region = { + id: 1, + region: 0, + description: 'Region 3', + maintenance_zone_id: 1, + }; + + const scanItemsCount = 3; + const zonesCount = 1; + const miqSchedulesCount = 0; + + afterEach(() => { + fetchMock.reset(); + fetchMock.restore(); + }); + + it('should render settings details tab', async() => { + const wrapper = shallow(); + + await new Promise((resolve) => { + setImmediate(() => { + wrapper.update(); + expect(toJson(wrapper)).toMatchSnapshot(); + resolve(); + }); + }); + }); +}); diff --git a/app/stylesheet/settings.scss b/app/stylesheet/settings.scss index 36dcfeadbab..d55ecf4e42a 100644 --- a/app/stylesheet/settings.scss +++ b/app/stylesheet/settings.scss @@ -45,3 +45,7 @@ color: red; } } + +.bx--front-line { + margin-left: 10px; +} diff --git a/app/views/ops/_settings_details_tab.html.haml b/app/views/ops/_settings_details_tab.html.haml index a32d1a740c1..d607edca29d 100644 --- a/app/views/ops/_settings_details_tab.html.haml +++ b/app/views/ops/_settings_details_tab.html.haml @@ -7,32 +7,9 @@ %table.table.table-striped.table-bordered.table-hover %tbody - - unless @edit - %tr - %td.table-view-pf-select{:onclick => remote_function(:url => {:action => 'region_edit', :id => region.id}, - :loading => "miqSparkle(true);"), - :title => _("Edit this Region")} - %i.pficon.pficon-regions - %td{:onclick => remote_function(:url => {:action => 'region_edit', :id => region.id}, - :loading => "miqSparkle(true);"), - :title => _("Edit this Region")} - = h(region.description) - [#{h(region.region)}] - %tr - %td.table-view-pf-select{:onclick => "miqTreeActivateNode('settings_tree', 'xx-sis');", :title => _("View Analysis Profiles")} - %i.fa.fa-search - %td{:onclick => "miqTreeActivateNode('settings_tree', 'xx-sis');", :title => _("View Analysis Profiles")} - = _("Analysis Profiles") - (#{h(@scan_items.size)}) - %tr - %td.table-view-pf-select{:onclick => "miqTreeActivateNode('settings_tree', 'xx-z');", :title => _("View Zones")} - %i.pficon.pficon-zone - %td{:onclick => "miqTreeActivateNode('settings_tree', 'xx-z');", :title => _("View Zones")} - = _("Zones") - (#{h(@zones.size)}) - %tr - %td.table-view-pf-select{:onclick => "miqTreeActivateNode('settings_tree', 'xx-msc');", :title => _("View Schedules")} - %i.fa.fa-clock-o - %td{:onclick => "miqTreeActivateNode('settings_tree', 'xx-msc');", :title => _("View Schedules")} - = _("Schedules") - (#{h(@miq_schedules.size)}) + = react('SettingsDetailsTab', + :region => region, + :scanItemsCount => @scan_items.size, + :zonesCount => @zones.size, + :miqSchedulesCount => @miq_schedules.size) + \ No newline at end of file diff --git a/app/views/shared/buttons/_ab_form.html.haml b/app/views/shared/buttons/_ab_form.html.haml index 3ee6365428b..00d1d918132 100644 --- a/app/views/shared/buttons/_ab_form.html.haml +++ b/app/views/shared/buttons/_ab_form.html.haml @@ -1,25 +1,4 @@ #ab_form - #policy_bar - - if session[:resolve_object].present? - - copied_target_class = session[:resolve_object][:new][:target_class] - - current_target_class = @edit[:new][:target_class] - - if copied_target_class == current_target_class - = link_to({:action => "resolve", :button => "paste"}, - "data-miq_sparkle_on" => true, - "data-miq_sparkle_off" => true, - :remote => true, - "data-method" => :post, - :class => 'btn btn-default', - :title => _("Paste object details for use in a Button.")) do - %i.fa.fa-clipboard - - else - %button.btn.btn-default.disabled{:title => _("Paste is not available, target class differs from the target class of the object copied from the Simulation screen")} - %i.fa.fa-clipboard - - else - %button.btn.btn-default.disabled{:title => _("Paste is not available, no object information has been copied from the Simulation screen")} - %i.fa.fa-clipboard - = render :partial => "layouts/flash_msg" - #custom_button_tabs %ul.nav.nav-tabs{'role' => 'tablist'} = miq_tab_header('ab_options_tab', @sb[:active_tab]) do diff --git a/cypress/e2e/ui/Settings/Application-Settings/settings_details_tab.cy.js b/cypress/e2e/ui/Settings/Application-Settings/settings_details_tab.cy.js new file mode 100644 index 00000000000..1d6a0b2e116 --- /dev/null +++ b/cypress/e2e/ui/Settings/Application-Settings/settings_details_tab.cy.js @@ -0,0 +1,42 @@ +/* eslint-disable no-undef */ + +describe('Settings > Application Settings > Details', () => { + beforeEach(() => { + cy.login(); + cy.intercept('POST', '/ops/accordion_select?id=rbac_accord').as('accordion'); + cy.menu('Settings', 'Application Settings'); + cy.get('[data-nodeid="0.0"].node-treeview-settings_tree').contains('ManageIQ Region').click(); + cy.get('#explorer_title_text'); + }); + + describe('Settings Details Tab', () => { + it('Region row is replaced by region form when clicked', () => { + cy.get('.bx--front-line').contains('Region 0').click({force: true}); + cy.get('.bx--label').contains('Description').should('exist'); + }); + it('Clicks on analysis profiles row and reroutes', () => { + cy.get('.bx--front-line').contains('Analysis Profiles').click({force: true}); + cy.get('#explorer_title_text').contains('Settings Analysis Profiles').should('exist'); + }); + it('Clicks on zones row and reroutes', () => { + cy.get('.bx--front-line').contains('Zones').click({force: true}); + cy.get('#explorer_title_text').contains('Settings Zones').should('exist'); + }); + it('Clicks on schedules row and reroutes', () => { + cy.get('.bx--front-line').contains('Schedules').click({force: true}); + cy.get('#explorer_title_text').contains('Settings Schedules').should('exist'); + }); + it('Updates region name when changed', () => { + cy.get('.bx--front-line').contains('Region 0').click({force: true}); + cy.get('#description').clear().type('Region 1'); + cy.get('button.bx--btn.bx--btn--primary').contains('Save').should('not.be.disabled').click(); + cy.get('.bx--front-line').contains('Region 1').should('exist'); + + // Clean up + cy.get('.bx--front-line').contains('Region 1').click({force: true}); + cy.get('#description').clear().type('Region 0'); + cy.get('button.bx--btn.bx--btn--primary').contains('Save').should('not.be.disabled').click(); + cy.get('.bx--front-line').contains('Region 0').should('exist'); + }); + }); +}); diff --git a/spec/helpers/application_helper/buttons/ae_copy_simulate_spec.rb b/spec/helpers/application_helper/buttons/ae_copy_simulate_spec.rb deleted file mode 100644 index ffb21e42e0e..00000000000 --- a/spec/helpers/application_helper/buttons/ae_copy_simulate_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -describe ApplicationHelper::Button::AeCopySimulate do - let(:view_context) { setup_view_context_with_sandbox({}) } - let(:resolve) { {:button_class => button_class} } - let(:button) { described_class.new(view_context, {}, {'resolve' => resolve}, {}) } - - describe '#disabled?' do - context 'when object attribute is specified' do - let(:button_class) { 'some_button_class' } - it_behaves_like 'an enabled button' - end - context 'when object attribute is not specified' do - let(:button_class) { nil } - it_behaves_like 'a disabled button', - 'Object attribute must be specified to copy object details for use in a Button' - end - end -end diff --git a/spec/views/shared/buttons/_ab_form.html.haml_spec.rb b/spec/views/shared/buttons/_ab_form.html.haml_spec.rb deleted file mode 100644 index 663090c9f85..00000000000 --- a/spec/views/shared/buttons/_ab_form.html.haml_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -describe "shared/buttons/_ab_form.html.haml" do - before do - set_controller_for_view("miq_ae_customization") - assign(:sb, :active_tab => "ab_options_tab") - assign(:edit, :new => {:target_class => "CloudNetwork"}) - assign(:resolve, :target_classes => [ - ["Availability Zone", "AvailabilityZone"], - ["Cloud Network", "CloudNetwork"], - ["VM Template and Image", "MiqTemplate"], - ["VM and Instance", "Vm"]]) - stub_template "shared/buttons/_ab_options_form.html.haml" => "" - stub_template "shared/buttons/_ab_advanced_form.html.haml" => "" - end - - describe "Paste button" do - it "is enabled if the copied target class is the same as the current target class" do - allow(view).to receive(:session) - .and_return(:resolve_object => {:new => {:target_class => "CloudNetwork"}}) - render :template => "shared/buttons/_ab_form" - expect(rendered).to include("Paste object details for use in a Button.") - end - - it "is disabled if the copied target class differs from the current target class" do - allow(view).to receive(:session) - .and_return(:resolve_object => {:new => {:target_class => "AvailabilityZone"}}) - render :template => "shared/buttons/_ab_form" - expect(rendered).to include("Paste is not available, target class differs from the target class of the object copied from the Simulation screen") - end - end -end