mirror of
https://github.com/ansible/awx.git
synced 2026-05-07 09:27:36 -02:30
Merge pull request #9439 from mabashian/9410-workflow-node-disable-jt
Disable job templates in node modal that are missing inv or project Reviewed-by: Mat Wilson <mawilson@redhat.com> https://github.com/one-t
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import styled from 'styled-components';
|
||||||
import {
|
import {
|
||||||
DataListItem,
|
DataListItem,
|
||||||
DataListItemRow,
|
DataListItemRow,
|
||||||
@@ -9,6 +10,14 @@ import {
|
|||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
import DataListCell from '../DataListCell';
|
import DataListCell from '../DataListCell';
|
||||||
|
|
||||||
|
const Label = styled.label`
|
||||||
|
${({ isDisabled }) =>
|
||||||
|
isDisabled &&
|
||||||
|
`
|
||||||
|
opacity: 0.5;
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
const CheckboxListItem = ({
|
const CheckboxListItem = ({
|
||||||
isDisabled = false,
|
isDisabled = false,
|
||||||
isRadio = false,
|
isRadio = false,
|
||||||
@@ -32,7 +41,7 @@ const CheckboxListItem = ({
|
|||||||
aria-label={`check-action-item-${itemId}`}
|
aria-label={`check-action-item-${itemId}`}
|
||||||
aria-labelledby={`check-action-item-${itemId}`}
|
aria-labelledby={`check-action-item-${itemId}`}
|
||||||
checked={isSelected}
|
checked={isSelected}
|
||||||
disabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
id={`selected-${itemId}`}
|
id={`selected-${itemId}`}
|
||||||
isChecked={isSelected}
|
isChecked={isSelected}
|
||||||
name={name}
|
name={name}
|
||||||
@@ -42,13 +51,14 @@ const CheckboxListItem = ({
|
|||||||
<DataListItemCells
|
<DataListItemCells
|
||||||
dataListCells={[
|
dataListCells={[
|
||||||
<DataListCell key="name">
|
<DataListCell key="name">
|
||||||
<label
|
<Label
|
||||||
id={`check-action-item-${itemId}`}
|
id={`check-action-item-${itemId}`}
|
||||||
htmlFor={`selected-${itemId}`}
|
htmlFor={`selected-${itemId}`}
|
||||||
className="check-action-item"
|
className="check-action-item"
|
||||||
|
isDisabled={isDisabled}
|
||||||
>
|
>
|
||||||
<b>{label}</b>
|
<b>{label}</b>
|
||||||
</label>
|
</Label>
|
||||||
</DataListCell>,
|
</DataListCell>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ const mockJobTemplate = {
|
|||||||
},
|
},
|
||||||
related: { webhook_receiver: '' },
|
related: { webhook_receiver: '' },
|
||||||
inventory: 1,
|
inventory: 1,
|
||||||
|
project: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('NodeModal', () => {
|
describe('NodeModal', () => {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useLocation } from 'react-router-dom';
|
|||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { func, shape } from 'prop-types';
|
import { func, shape } from 'prop-types';
|
||||||
|
import { Tooltip } from '@patternfly/react-core';
|
||||||
import { JobTemplatesAPI } from '../../../../../../api';
|
import { JobTemplatesAPI } from '../../../../../../api';
|
||||||
import { getQSConfig, parseQueryString } from '../../../../../../util/qs';
|
import { getQSConfig, parseQueryString } from '../../../../../../util/qs';
|
||||||
import useRequest from '../../../../../../util/useRequest';
|
import useRequest from '../../../../../../util/useRequest';
|
||||||
@@ -56,26 +57,56 @@ function JobTemplatesList({ i18n, nodeResource, onUpdateNodeResource }) {
|
|||||||
fetchJobTemplates();
|
fetchJobTemplates();
|
||||||
}, [fetchJobTemplates]);
|
}, [fetchJobTemplates]);
|
||||||
|
|
||||||
|
const onSelectRow = row => {
|
||||||
|
if (
|
||||||
|
row.project &&
|
||||||
|
row.project !== null &&
|
||||||
|
((row.inventory && row.inventory !== null) || row.ask_inventory_on_launch)
|
||||||
|
) {
|
||||||
|
onUpdateNodeResource(row);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PaginatedDataList
|
<PaginatedDataList
|
||||||
contentError={error}
|
contentError={error}
|
||||||
hasContentLoading={isLoading}
|
hasContentLoading={isLoading}
|
||||||
itemCount={count}
|
itemCount={count}
|
||||||
items={jobTemplates}
|
items={jobTemplates}
|
||||||
onRowClick={row => onUpdateNodeResource(row)}
|
onRowClick={row => onSelectRow(row)}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
renderItem={item => (
|
renderItem={item => {
|
||||||
<CheckboxListItem
|
const isDisabled =
|
||||||
isSelected={!!(nodeResource && nodeResource.id === item.id)}
|
!item.project ||
|
||||||
itemId={item.id}
|
item.project === null ||
|
||||||
key={item.id}
|
((!item.inventory || item.inventory === null) &&
|
||||||
name={item.name}
|
!item.ask_inventory_on_launch);
|
||||||
label={item.name}
|
const listItem = (
|
||||||
onSelect={() => onUpdateNodeResource(item)}
|
<CheckboxListItem
|
||||||
onDeselect={() => onUpdateNodeResource(null)}
|
isDisabled={isDisabled}
|
||||||
isRadio
|
isSelected={!!(nodeResource && nodeResource.id === item.id)}
|
||||||
/>
|
itemId={item.id}
|
||||||
)}
|
key={`${item.id}-listItem`}
|
||||||
|
name={item.name}
|
||||||
|
label={item.name}
|
||||||
|
onSelect={() => onSelectRow(item)}
|
||||||
|
onDeselect={() => onUpdateNodeResource(null)}
|
||||||
|
isRadio
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
return isDisabled ? (
|
||||||
|
<Tooltip
|
||||||
|
key={`${item.id}-tooltip`}
|
||||||
|
content={i18n._(
|
||||||
|
t`Job Templates with a missing inventory or project cannot be selected when creating or editing nodes`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{listItem}
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
listItem
|
||||||
|
);
|
||||||
|
}}
|
||||||
renderToolbar={props => <DataListToolbar {...props} fillWidth />}
|
renderToolbar={props => <DataListToolbar {...props} fillWidth />}
|
||||||
showPageSizeOptions={false}
|
showPageSizeOptions={false}
|
||||||
toolbarSearchColumns={[
|
toolbarSearchColumns={[
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const onUpdateNodeResource = jest.fn();
|
|||||||
describe('JobTemplatesList', () => {
|
describe('JobTemplatesList', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
});
|
});
|
||||||
test('Row selected when nodeResource id matches row id and clicking new row makes expected callback', async () => {
|
test('Row selected when nodeResource id matches row id and clicking new row makes expected callback', async () => {
|
||||||
@@ -28,12 +29,16 @@ describe('JobTemplatesList', () => {
|
|||||||
name: 'Test Job Template',
|
name: 'Test Job Template',
|
||||||
type: 'job_template',
|
type: 'job_template',
|
||||||
url: '/api/v2/job_templates/1',
|
url: '/api/v2/job_templates/1',
|
||||||
|
inventory: 1,
|
||||||
|
project: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'Test Job Template 2',
|
name: 'Test Job Template 2',
|
||||||
type: 'job_template',
|
type: 'job_template',
|
||||||
url: '/api/v2/job_templates/2',
|
url: '/api/v2/job_templates/2',
|
||||||
|
inventory: 1,
|
||||||
|
project: 2,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -60,10 +65,18 @@ describe('JobTemplatesList', () => {
|
|||||||
wrapper.find('CheckboxListItem[name="Test Job Template"]').props()
|
wrapper.find('CheckboxListItem[name="Test Job Template"]').props()
|
||||||
.isSelected
|
.isSelected
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
wrapper.find('CheckboxListItem[name="Test Job Template"]').props()
|
||||||
|
.isDisabled
|
||||||
|
).toBe(false);
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('CheckboxListItem[name="Test Job Template 2"]').props()
|
wrapper.find('CheckboxListItem[name="Test Job Template 2"]').props()
|
||||||
.isSelected
|
.isSelected
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
wrapper.find('CheckboxListItem[name="Test Job Template 2"]').props()
|
||||||
|
.isDisabled
|
||||||
|
).toBe(false);
|
||||||
wrapper
|
wrapper
|
||||||
.find('CheckboxListItem[name="Test Job Template 2"]')
|
.find('CheckboxListItem[name="Test Job Template 2"]')
|
||||||
.simulate('click');
|
.simulate('click');
|
||||||
@@ -72,8 +85,75 @@ describe('JobTemplatesList', () => {
|
|||||||
name: 'Test Job Template 2',
|
name: 'Test Job Template 2',
|
||||||
type: 'job_template',
|
type: 'job_template',
|
||||||
url: '/api/v2/job_templates/2',
|
url: '/api/v2/job_templates/2',
|
||||||
|
inventory: 1,
|
||||||
|
project: 2,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
test('Row disabled when job template missing inventory or project', async () => {
|
||||||
|
JobTemplatesAPI.read.mockResolvedValueOnce({
|
||||||
|
data: {
|
||||||
|
count: 2,
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Test Job Template',
|
||||||
|
type: 'job_template',
|
||||||
|
url: '/api/v2/job_templates/1',
|
||||||
|
inventory: 1,
|
||||||
|
project: null,
|
||||||
|
ask_inventory_on_launch: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Test Job Template 2',
|
||||||
|
type: 'job_template',
|
||||||
|
url: '/api/v2/job_templates/2',
|
||||||
|
inventory: null,
|
||||||
|
project: 2,
|
||||||
|
ask_inventory_on_launch: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
JobTemplatesAPI.readOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: {},
|
||||||
|
POST: {},
|
||||||
|
},
|
||||||
|
related_search_fields: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<JobTemplatesList
|
||||||
|
nodeResource={nodeResource}
|
||||||
|
onUpdateNodeResource={onUpdateNodeResource}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(
|
||||||
|
wrapper.find('CheckboxListItem[name="Test Job Template"]').props()
|
||||||
|
.isSelected
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
wrapper.find('CheckboxListItem[name="Test Job Template"]').props()
|
||||||
|
.isDisabled
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
wrapper.find('CheckboxListItem[name="Test Job Template 2"]').props()
|
||||||
|
.isSelected
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
wrapper.find('CheckboxListItem[name="Test Job Template 2"]').props()
|
||||||
|
.isDisabled
|
||||||
|
).toBe(true);
|
||||||
|
wrapper
|
||||||
|
.find('CheckboxListItem[name="Test Job Template 2"]')
|
||||||
|
.simulate('click');
|
||||||
|
expect(onUpdateNodeResource).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
test('Error shown when read() request errors', async () => {
|
test('Error shown when read() request errors', async () => {
|
||||||
JobTemplatesAPI.read.mockRejectedValue(new Error());
|
JobTemplatesAPI.read.mockRejectedValue(new Error());
|
||||||
JobTemplatesAPI.readOptions.mockResolvedValue({
|
JobTemplatesAPI.readOptions.mockResolvedValue({
|
||||||
|
|||||||
Reference in New Issue
Block a user