Skip to content

Commit

Permalink
Fixes #37228 - Replace Ansible/Roles page with React component
Browse files Browse the repository at this point in the history
  • Loading branch information
Thorben-D committed Jun 21, 2024
1 parent 6cd6fe5 commit d2b6c5c
Show file tree
Hide file tree
Showing 18 changed files with 391 additions and 93 deletions.
15 changes: 2 additions & 13 deletions app/controllers/ansible_roles_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,9 @@ class AnsibleRolesController < ::ApplicationController
include Foreman::Controller::AutoCompleteSearch
include ForemanAnsible::Concerns::ImportControllerHelper
include ::ForemanAnsible::AnsibleRolesDataPreparations
def index
@ansible_roles = resource_base.search_for(params[:search],
:order => params[:order]).
paginate(:page => params[:page],
:per_page => params[:per_page])
end
def index; end

def destroy
if @ansible_role.destroy
process_success
else
process_error
end
end
def destroy; end

def import
changed = @importer.import!
Expand Down
8 changes: 8 additions & 0 deletions app/controllers/ui_ansible_roles_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class UiAnsibleRolesController < ::Api::V2::BaseController
before_action :find_resource, :only => [:destroy]

def resource_name(resource = 'AnsibleRole')
super resource
end
Expand All @@ -7,6 +9,12 @@ def index
@ui_ansible_roles = resource_scope_for_index(:permission => :view_ansible_roles)
end

api :DELETE, '/ui_ansible_roles/:id', N_('Deletes Ansible role')
param :id, :identifier, :required => true
def destroy
AnsibleRole.find_by!(id: params[:id]).destroy
end

# restore original method from find_common to ignore resource nesting
def resource_scope(**kwargs)
@resource_scope ||= scope_for(resource_class, **kwargs)
Expand Down
47 changes: 0 additions & 47 deletions app/views/ansible_roles/index.html.erb

This file was deleted.

14 changes: 0 additions & 14 deletions app/views/ansible_roles/welcome.html.erb

This file was deleted.

14 changes: 13 additions & 1 deletion app/views/ui_ansible_roles/show.json.rabl
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
object @ansible_role

extends "api/v2/ansible_roles/show"
attributes :id, :name, :updated_at
code :hostgroups_count do |role|
role.hostgroups.count
end
code :hosts_count do |role|
role.hosts.count
end
code :variables_count do |role|
role.ansible_variables.count
end
code :can_delete do
authorized_for(:controller => UiAnsibleRolesController, :action => :destroy)
end
3 changes: 2 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
end
end
scope '/ansible' do
match '/ansible_roles' => 'react#index', :via => [:get]
constraints(:id => %r{[^\/]+}) do
resources :hosts, :only => [] do
member do
Expand All @@ -60,7 +61,7 @@
end
end

resources :ui_ansible_roles, :only => [:index]
resources :ui_ansible_roles, :only => [:index, :destroy]

resources :ansible_variables, :except => [:show] do
resources :lookup_values, :only => [:index, :create, :update, :destroy]
Expand Down
3 changes: 2 additions & 1 deletion lib/foreman_ansible/register.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@
:resource_type => 'AnsibleRole'
permission :destroy_ansible_roles,
{ :ansible_roles => [:destroy],
:'api/v2/ansible_roles' => [:destroy, :obsolete] },
:'api/v2/ansible_roles' => [:destroy, :obsolete],
:ui_ansible_roles => [:destroy] },
:resource_type => 'AnsibleRole'
permission :import_ansible_roles,
{ :ansible_roles => [:import, :confirm_import],
Expand Down
17 changes: 1 addition & 16 deletions test/functional/ansible_roles_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,4 @@

require 'test_plugin_helper'
# functional tests for AnsibleRolesController
class AnsibleRolesControllerTest < ActionController::TestCase
setup do
@role = FactoryBot.create(:ansible_role)
end

basic_index_test

test 'should destroy role' do
assert_difference('AnsibleRole.count', -1) do
delete :destroy,
:params => { :id => @role.id },
:session => set_session_user
end
assert_redirected_to ansible_roles_url
end
end
class AnsibleRolesControllerTest < ActionController::TestCase; end
7 changes: 7 additions & 0 deletions test/functional/ui_ansible_roles_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,11 @@ class UiAnsibleRolesControllerTest < ActionController::TestCase
res = JSON.parse @response.body
assert_equal res['total'], res['results'].size
end

test 'should destroy role' do
delete :destroy,
:params => { :id => @role.id },
:session => set_session_user
assert_response :success
end
end
40 changes: 40 additions & 0 deletions webpack/components/AnsibleRoles/AnsibleRolesPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import TableIndexPage from 'foremanReact/components/PF4/TableIndexPage/TableIndexPage';
import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
import { translate as __ } from 'foremanReact/common/I18n';
import { getDocsURL } from 'foremanReact/common/helpers';
import { getActions } from './AnsibleRolesPageHelpers';
import { AnsibleRolesImportButtonWrapper } from './components/AnsibleRolesImportButtonWrapper';

export const AnsibleRolesPage = () => (
<TableIndexPage
header={__('Ansible Roles')}
controller="ansible_roles"
apiUrl="/ansible/ui_ansible_roles"
apiOptions={{ key: 'ANSIBLE_ROLES_API_REQUEST_KEY' }}
hasHelpPage
creatable={false}
isDeleteable
searchable
customToolbarItems={<AnsibleRolesImportButtonWrapper />}
customHelpURL={getDocsURL(
'Managing_Configurations_Ansible',
'Importing_Ansible_Roles_and_Variables_ansible'
)}
rowKebabItems={getActions}
columns={{
name: { title: __('Name'), isSorted: true, weight: 0 },
hostgroups_count: { title: __('Hostgroups'), weight: 1 },
hosts_count: { title: __('Hosts'), weight: 2 },
variables_count: { title: __('Variables'), weight: 3 },
updated_at: {
title: __('Imported at'),
isSorted: true,
weight: 4,
wrapper: ({ updated_at: updatedAt }) => (
<RelativeDateTime date={new Date(updatedAt)} />
),
},
}}
/>
);
38 changes: 38 additions & 0 deletions webpack/components/AnsibleRoles/AnsibleRolesPageHelpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import { translate as __ } from 'foremanReact/common/I18n';

export const getActions = ({ id, name, canDelete, ...item }) => [
{
title: (
<a href={hostgroupsUrl(name)} target="_blank" rel="noreferrer">
{__('View Hostgroups')}
</a>
),
isDisabled: false,
},
{
title: (
<a href={hostsUrl(name)} target="_blank" rel="noreferrer">
{__('View Hosts')}
</a>
),
isDisabled: false,
},
{
title: (
<a href={variablesUrl(name)} target="_blank" rel="noreferrer">
{__('View Variables')}
</a>
),
isDisabled: false,
},
];

const hostgroupsUrl = roleName => `/hostgroups${searchString(roleName)}`;
const hostsUrl = roleName => `/hosts${searchString(roleName)}`;
const variablesUrl = roleName => `ansible_variables${searchString(roleName)}`;
const searchString = roleName =>
`?search=${encodeURIComponent(`ansible_role = ${roleName}`)}`;

export const importUrl = (proxyName, proxyId) =>
`ansible_roles/import?proxy=${encodeURIComponent(`${proxyId}-${proxyName}`)}`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export const testSmartProxies = [
{
created_at: '2024-06-21 14:49:35 +0200',
updated_at: '2024-06-21 14:49:35 +0200',
hosts_count: 0,
name: 'debuggable',
id: 2,
url: 'http://smart-proxy-ansible-capable.example.com:8080',
remote_execution_pubkey: null,
download_policy: 'on_demand',
supported_pulp_types: [],
lifecycle_environments: [],
features: [
{
capabilities: ['ansible-runner', 'single'],
name: 'Dynflow',
id: 17,
},
{
capabilities: ['vcs_clone'],
name: 'Ansible',
id: 20,
},
{
capabilities: [],
name: 'Logs',
id: 13,
},
],
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import { Dropdown, DropdownToggle, DropdownItem } from '@patternfly/react-core';
import { translate as __ } from 'foremanReact/common/I18n';
import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
import { importUrl } from '../AnsibleRolesPageHelpers';
import { showToast } from '../../../toastHelper';

export const AnsibleRolesImportButton = () => {
let dropdownItems;

const {
response: { results },
status,
} = useAPI('get', '/api/v2/smart_proxies', {
params: { search: 'feature=Ansible', per_page: 'all' },
});

const [isOpen, setIsOpen] = React.useState(false);
const onToggle = isToggleOpen => {
setIsOpen(isToggleOpen);
};
const onFocus = () => {
const element = document.getElementById('toggle-primary');
element.focus();
};
const onSelect = () => {
setIsOpen(false);
onFocus();
};

if (status === 'PENDING') {
dropdownItems = [
<DropdownItem isDisabled key="loading_item">
{__('Loading...')}
</DropdownItem>,
];
} else if (status === 'ERROR') {
showToast({
type: 'error',
message: __('There was an error requesting Smart Proxies'),
});
} else if (status === 'RESOLVED') {
dropdownItems = results.map(proxy => (
<DropdownItem key={proxy.name} href={importUrl(proxy.name, proxy.id)}>
{proxy.name}
</DropdownItem>
));
if (dropdownItems.length === 0) {
dropdownItems = [
<DropdownItem isDisabled key="none_found">
{__('No Smart Proxies found')}
</DropdownItem>,
];
}
}
return (
<Dropdown
onSelect={onSelect}
toggle={
<DropdownToggle
id="toggle-primary"
toggleVariant="primary"
onToggle={onToggle}
>
{__('Import from')}
</DropdownToggle>
}
isOpen={isOpen}
dropdownItems={dropdownItems}
/>
);
};
Loading

0 comments on commit d2b6c5c

Please sign in to comment.