Skip to content

Commit

Permalink
Fixes #36887 - Add Schedule a Job to new host overview (#845)
Browse files Browse the repository at this point in the history
* Fixes #36887 - Add Schedule a Job to new host overview

* Refs #36887 - ensure dropdown isn't disabled on host details page

* Refs #36887 - hide dropdown based on permissions

* Refs #36887 - use general permission, not per-host
  • Loading branch information
jeremylenz authored Nov 22, 2023
1 parent 29fbd3a commit f7e62ce
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 19 deletions.
12 changes: 12 additions & 0 deletions app/models/concerns/api/v2/hosts_controller_extensions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Api
module V2
module HostsControllerExtensions
extend ActiveSupport::Concern
def index_node_permissions
super.merge({
:can_create_job_invocations => authorized_for(:controller => 'job_invocations', :action => 'create'),
})
end
end
end
end
1 change: 1 addition & 0 deletions lib/foreman_remote_execution/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ class Engine < ::Rails::Engine

ForemanRemoteExecution.register_rex_feature

::Api::V2::HostsController.include Api::V2::HostsControllerExtensions
::Api::V2::SubnetsController.include ::ForemanRemoteExecution::Concerns::Api::V2::SubnetsControllerExtensions
::Api::V2::RegistrationController.prepend ::ForemanRemoteExecution::Concerns::Api::V2::RegistrationControllerExtensions
::Api::V2::RegistrationController.include ::ForemanRemoteExecution::Concerns::Api::V2::RegistrationControllerExtensions::ApipieExtensions
Expand Down
6 changes: 4 additions & 2 deletions webpack/react_app/components/FeaturesDropdown/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { foremanUrl } from 'foremanReact/common/helpers';
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
import { post } from 'foremanReact/redux/API';

export const runFeature = (hostId, feature, label) => dispatch => {
export const runFeature = (hostId, feature, label, hostSearch) => dispatch => {
const url = foremanUrl(
`/job_invocations?feature=${feature}&host_ids%5B%5D=${hostId}`
hostId
? `/job_invocations?feature=${feature}&host_ids%5B%5D=${hostId}`
: `/job_invocations?feature=${feature}&search=${hostSearch}`
);
const redirectUrl = 'job_invocations/new';

Expand Down
3 changes: 0 additions & 3 deletions webpack/react_app/components/FeaturesDropdown/constant.js

This file was deleted.

5 changes: 5 additions & 0 deletions webpack/react_app/components/FeaturesDropdown/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const REX_FEATURES_HOST_URL = host =>
`/api/v2/hosts/${host}/available_remote_execution_features`;
export const ALL_REX_FEATURES_URL = '/api/v2/remote_execution_features';
export const NEW_JOB_PAGE = '/job_invocations/new?host_ids%5B%5D';
export const ALL_HOSTS_NEW_JOB_PAGE = '/job_invocations/new?search';
67 changes: 53 additions & 14 deletions webpack/react_app/components/FeaturesDropdown/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,58 +11,83 @@ import { push } from 'connected-react-router';

import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
import { translate as __ } from 'foremanReact/common/I18n';
import { foremanUrl } from 'foremanReact/common/helpers';
import { foremanUrl, propsToCamelCase } from 'foremanReact/common/helpers';
import { STATUS } from 'foremanReact/constants';

import { REX_FEATURES_API, NEW_JOB_PAGE } from './constant';
import {
REX_FEATURES_HOST_URL,
ALL_REX_FEATURES_URL,
NEW_JOB_PAGE,
ALL_HOSTS_NEW_JOB_PAGE,
} from './constants';
import { runFeature } from './actions';
import './index.scss';

const FeaturesDropdown = ({ hostId }) => {
const FeaturesDropdown = ({
hostId,
hostSearch,
hostResponse,
selectedCount,
}) => {
const [isOpen, setIsOpen] = useState(false);
const { response, status } = useAPI(
'get',
foremanUrl(REX_FEATURES_API(hostId))
);
const isSingleHost = !!hostId; // identifies whether we're on the host details or host overview page
const rexFeaturesUrl = isSingleHost
? REX_FEATURES_HOST_URL(hostId)
: ALL_REX_FEATURES_URL;
const { response, status } = useAPI('get', foremanUrl(rexFeaturesUrl));
const dispatch = useDispatch();
// eslint-disable-next-line camelcase
const canRunJob = response?.permissions?.can_run_job;
const permissions = propsToCamelCase(
(isSingleHost ? response?.permissions : hostResponse?.response) || {}
);
const canRunJob = isSingleHost
? permissions.canRunJob
: permissions.canCreateJobInvocations;
if (!canRunJob) {
return null;
}
// eslint-disable-next-line camelcase
const features = response?.remote_execution_features;

const features = isSingleHost
? response?.remote_execution_features // eslint-disable-line camelcase
: response?.results;
const dropdownItems = features
?.filter(feature => feature.host_action_button)
?.map(({ name, label, id, description }) => (
<DropdownItem
onClick={() => dispatch(runFeature(hostId, label, name))}
onClick={() => dispatch(runFeature(hostId, label, name, hostSearch))}
key={id}
description={description}
>
{name}
</DropdownItem>
));
const newJobPageUrl = hostId
? `${NEW_JOB_PAGE}=${hostId}`
: `${ALL_HOSTS_NEW_JOB_PAGE}=${hostSearch}`;
const scheduleJob = [
<DropdownToggleAction
onClick={() => dispatch(push(`${NEW_JOB_PAGE}=${hostId}`))}
onClick={() => dispatch(push(newJobPageUrl))}
key="schedule-job-action"
>
{__('Schedule a job')}
</DropdownToggleAction>,
];

const disableDropdown = !isSingleHost && selectedCount === 0;

return (
<Dropdown
ouiaId="schedule-a-job-dropdown"
id="schedule-a-job-dropdown"
alignments={{ default: 'right' }}
onSelect={() => setIsOpen(false)}
toggle={
<DropdownToggle
ouiaId="schedule-a-job-dropdown-toggle"
id="schedule-a-job-dropdown-toggle"
splitButtonItems={scheduleJob}
toggleVariant="secondary"
onToggle={() => setIsOpen(prev => !prev)}
isDisabled={status === STATUS.PENDING}
isDisabled={status === STATUS.PENDING || disableDropdown}
splitButtonVariant="action"
/>
}
Expand All @@ -74,9 +99,23 @@ const FeaturesDropdown = ({ hostId }) => {

FeaturesDropdown.propTypes = {
hostId: PropTypes.number,
hostSearch: PropTypes.string,
selectedCount: PropTypes.number,
hostResponse: PropTypes.shape({
response: PropTypes.shape({
results: PropTypes.arrayOf(
PropTypes.shape({
can_create_job_invocations: PropTypes.bool,
})
),
}),
}),
};
FeaturesDropdown.defaultProps = {
hostId: undefined,
hostSearch: undefined,
selectedCount: 0,
hostResponse: undefined,
};

export default FeaturesDropdown;
11 changes: 11 additions & 0 deletions webpack/react_app/components/FeaturesDropdown/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#schedule-a-job-dropdown ul.pf-c-dropdown__menu {
padding-left: 0;
li {
display: unset;
a {
font-size: 16px;
color: var(--pf-c-dropdown__menu-item--Color);
font-weight: 300;
}
}
}
6 changes: 6 additions & 0 deletions webpack/react_app/extend/Fills.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ const fills = [
component: props => <FeaturesDropdown {...props} />,
weight: 1000,
},
{
slot: '_all-hosts-schedule-a-job',
name: '_all-hosts-schedule-a-job',
component: props => <FeaturesDropdown {...props} />,
weight: 1000,
},
];

const registerFills = () => {
Expand Down

0 comments on commit f7e62ce

Please sign in to comment.