mirror of
https://github.com/ansible/awx.git
synced 2026-05-09 02:17:37 -02:30
Add several changes to Instance Groups
Add several changes to API and UI related to Instance Groups. * Update summary_fields for DEFAULT_CONTROL_PLANE_QUEUE_NAME, and DEFAULT_EXECUTION_QUEUE_NAME. Rely on API validation for those fields. * Fix Instance Group list RBAC * Add validation for a couple of fields on the Instance Groups endpoint 1. is_container_group 2. policy_instance_percentage 3. policy_instance_list See: https://github.com/ansible/awx/issues/11130 Also: https://github.com/ansible/awx/issues/11718
This commit is contained in:
@@ -4,8 +4,6 @@
|
|||||||
# Python
|
# Python
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework.exceptions import MethodNotAllowed, PermissionDenied
|
from rest_framework.exceptions import MethodNotAllowed, PermissionDenied
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
@@ -250,13 +248,6 @@ class IsSystemAdminOrAuditor(permissions.BasePermission):
|
|||||||
return request.user.is_superuser
|
return request.user.is_superuser
|
||||||
|
|
||||||
|
|
||||||
class InstanceGroupTowerPermission(ModelAccessPermission):
|
|
||||||
def has_object_permission(self, request, view, obj):
|
|
||||||
if request.method == 'DELETE' and obj.name in [settings.DEFAULT_EXECUTION_QUEUE_NAME, settings.DEFAULT_CONTROL_PLANE_QUEUE_NAME]:
|
|
||||||
return False
|
|
||||||
return super(InstanceGroupTowerPermission, self).has_object_permission(request, view, obj)
|
|
||||||
|
|
||||||
|
|
||||||
class WebhookKeyPermission(permissions.BasePermission):
|
class WebhookKeyPermission(permissions.BasePermission):
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
return request.user.can_access(view.model, 'admin', obj, request.data)
|
return request.user.can_access(view.model, 'admin', obj, request.data)
|
||||||
|
|||||||
@@ -4947,6 +4947,9 @@ class InstanceGroupSerializer(BaseSerializer):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def validate_policy_instance_list(self, value):
|
def validate_policy_instance_list(self, value):
|
||||||
|
if self.instance and self.instance.name in [settings.DEFAULT_EXECUTION_QUEUE_NAME, settings.DEFAULT_CONTROL_PLANE_QUEUE_NAME]:
|
||||||
|
if self.instance.policy_instance_list != value:
|
||||||
|
raise serializers.ValidationError(_('%s instance group policy_instance_list may not be changed.' % self.instance.name))
|
||||||
for instance_name in value:
|
for instance_name in value:
|
||||||
if value.count(instance_name) > 1:
|
if value.count(instance_name) > 1:
|
||||||
raise serializers.ValidationError(_('Duplicate entry {}.').format(instance_name))
|
raise serializers.ValidationError(_('Duplicate entry {}.').format(instance_name))
|
||||||
@@ -4957,6 +4960,11 @@ class InstanceGroupSerializer(BaseSerializer):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
def validate_policy_instance_percentage(self, value):
|
def validate_policy_instance_percentage(self, value):
|
||||||
|
if self.instance and self.instance.name in [settings.DEFAULT_EXECUTION_QUEUE_NAME, settings.DEFAULT_CONTROL_PLANE_QUEUE_NAME]:
|
||||||
|
if value != self.instance.policy_instance_percentage:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
_('%s instance group policy_instance_percentage may not be changed from the initial value set by the installer.' % self.instance.name)
|
||||||
|
)
|
||||||
if value and self.instance and self.instance.is_container_group:
|
if value and self.instance and self.instance.is_container_group:
|
||||||
raise serializers.ValidationError(_('Containerized instances may not be managed via the API'))
|
raise serializers.ValidationError(_('Containerized instances may not be managed via the API'))
|
||||||
return value
|
return value
|
||||||
@@ -4975,6 +4983,13 @@ class InstanceGroupSerializer(BaseSerializer):
|
|||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def validate_is_container_group(self, value):
|
||||||
|
if self.instance and self.instance.name in [settings.DEFAULT_EXECUTION_QUEUE_NAME, settings.DEFAULT_CONTROL_PLANE_QUEUE_NAME]:
|
||||||
|
if value != self.instance.is_container_group:
|
||||||
|
raise serializers.ValidationError(_('%s instance group is_container_group may not be changed.' % self.instance.name))
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
def validate_credential(self, value):
|
def validate_credential(self, value):
|
||||||
if value and not value.kubernetes:
|
if value and not value.kubernetes:
|
||||||
raise serializers.ValidationError(_('Only Kubernetes credentials can be associated with an Instance Group'))
|
raise serializers.ValidationError(_('Only Kubernetes credentials can be associated with an Instance Group'))
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ from awx.api.permissions import (
|
|||||||
ProjectUpdatePermission,
|
ProjectUpdatePermission,
|
||||||
InventoryInventorySourcesUpdatePermission,
|
InventoryInventorySourcesUpdatePermission,
|
||||||
UserPermission,
|
UserPermission,
|
||||||
InstanceGroupTowerPermission,
|
|
||||||
VariableDataPermission,
|
VariableDataPermission,
|
||||||
WorkflowApprovalPermission,
|
WorkflowApprovalPermission,
|
||||||
IsSystemAdminOrAuditor,
|
IsSystemAdminOrAuditor,
|
||||||
@@ -480,7 +479,6 @@ class InstanceGroupDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAP
|
|||||||
name = _("Instance Group Detail")
|
name = _("Instance Group Detail")
|
||||||
model = models.InstanceGroup
|
model = models.InstanceGroup
|
||||||
serializer_class = serializers.InstanceGroupSerializer
|
serializer_class = serializers.InstanceGroupSerializer
|
||||||
permission_classes = (InstanceGroupTowerPermission,)
|
|
||||||
|
|
||||||
def update_raw_data(self, data):
|
def update_raw_data(self, data):
|
||||||
if self.get_object().is_container_group:
|
if self.get_object().is_container_group:
|
||||||
|
|||||||
@@ -465,7 +465,7 @@ class BaseAccess(object):
|
|||||||
if display_method == 'schedule':
|
if display_method == 'schedule':
|
||||||
user_capabilities['schedule'] = user_capabilities['start']
|
user_capabilities['schedule'] = user_capabilities['start']
|
||||||
continue
|
continue
|
||||||
elif display_method == 'delete' and not isinstance(obj, (User, UnifiedJob, CredentialInputSource, ExecutionEnvironment)):
|
elif display_method == 'delete' and not isinstance(obj, (User, UnifiedJob, CredentialInputSource, ExecutionEnvironment, InstanceGroup)):
|
||||||
user_capabilities['delete'] = user_capabilities['edit']
|
user_capabilities['delete'] = user_capabilities['edit']
|
||||||
continue
|
continue
|
||||||
elif display_method == 'copy' and isinstance(obj, (Group, Host)):
|
elif display_method == 'copy' and isinstance(obj, (Group, Host)):
|
||||||
@@ -575,6 +575,11 @@ class InstanceGroupAccess(BaseAccess):
|
|||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
return self.user.is_superuser
|
return self.user.is_superuser
|
||||||
|
|
||||||
|
def can_delete(self, obj):
|
||||||
|
if obj.name in [settings.DEFAULT_EXECUTION_QUEUE_NAME, settings.DEFAULT_CONTROL_PLANE_QUEUE_NAME]:
|
||||||
|
return False
|
||||||
|
return self.user.is_superuser
|
||||||
|
|
||||||
|
|
||||||
class UserAccess(BaseAccess):
|
class UserAccess(BaseAccess):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { CaretLeftIcon } from '@patternfly/react-icons';
|
|||||||
import { Card, PageSection } from '@patternfly/react-core';
|
import { Card, PageSection } from '@patternfly/react-core';
|
||||||
|
|
||||||
import useRequest from 'hooks/useRequest';
|
import useRequest from 'hooks/useRequest';
|
||||||
import { InstanceGroupsAPI, SettingsAPI } from 'api';
|
import { InstanceGroupsAPI } from 'api';
|
||||||
import RoutedTabs from 'components/RoutedTabs';
|
import RoutedTabs from 'components/RoutedTabs';
|
||||||
import ContentError from 'components/ContentError';
|
import ContentError from 'components/ContentError';
|
||||||
import ContentLoading from 'components/ContentLoading';
|
import ContentLoading from 'components/ContentLoading';
|
||||||
@@ -30,28 +30,15 @@ function ContainerGroup({ setBreadcrumb }) {
|
|||||||
isLoading,
|
isLoading,
|
||||||
error: contentError,
|
error: contentError,
|
||||||
request: fetchInstanceGroups,
|
request: fetchInstanceGroups,
|
||||||
result: { instanceGroup, defaultControlPlane, defaultExecution },
|
result: { instanceGroup },
|
||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const [
|
const { data } = await InstanceGroupsAPI.readDetail(id);
|
||||||
{ data },
|
|
||||||
{
|
|
||||||
data: {
|
|
||||||
DEFAULT_EXECUTION_QUEUE_NAME,
|
|
||||||
DEFAULT_CONTROL_PLANE_QUEUE_NAME,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] = await Promise.all([
|
|
||||||
InstanceGroupsAPI.readDetail(id),
|
|
||||||
SettingsAPI.readAll(),
|
|
||||||
]);
|
|
||||||
return {
|
return {
|
||||||
instanceGroup: data,
|
instanceGroup: data,
|
||||||
defaultControlPlane: DEFAULT_CONTROL_PLANE_QUEUE_NAME,
|
|
||||||
defaultExecution: DEFAULT_EXECUTION_QUEUE_NAME,
|
|
||||||
};
|
};
|
||||||
}, [id]),
|
}, [id]),
|
||||||
{ instanceGroup: null, defaultExecution: '' }
|
{ instanceGroup: null }
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -125,17 +112,10 @@ function ContainerGroup({ setBreadcrumb }) {
|
|||||||
{instanceGroup && (
|
{instanceGroup && (
|
||||||
<>
|
<>
|
||||||
<Route path="/instance_groups/container_group/:id/edit">
|
<Route path="/instance_groups/container_group/:id/edit">
|
||||||
<ContainerGroupEdit
|
<ContainerGroupEdit instanceGroup={instanceGroup} />
|
||||||
instanceGroup={instanceGroup}
|
|
||||||
defaultControlPlane={defaultControlPlane}
|
|
||||||
defaultExecution={defaultExecution}
|
|
||||||
/>
|
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/instance_groups/container_group/:id/details">
|
<Route path="/instance_groups/container_group/:id/details">
|
||||||
<ContainerGroupDetails
|
<ContainerGroupDetails instanceGroup={instanceGroup} />
|
||||||
instanceGroup={instanceGroup}
|
|
||||||
defaultExecution={defaultExecution}
|
|
||||||
/>
|
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/instance_groups/container_group/:id/jobs">
|
<Route path="/instance_groups/container_group/:id/jobs">
|
||||||
<JobList
|
<JobList
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { jsonToYaml, isJsonString } from 'util/yaml';
|
|||||||
|
|
||||||
import ContainerGroupForm from '../shared/ContainerGroupForm';
|
import ContainerGroupForm from '../shared/ContainerGroupForm';
|
||||||
|
|
||||||
function ContainerGroupAdd({ defaultExecution, defaultControlPlane }) {
|
function ContainerGroupAdd() {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [submitError, setSubmitError] = useState(null);
|
const [submitError, setSubmitError] = useState(null);
|
||||||
|
|
||||||
@@ -93,8 +93,6 @@ function ContainerGroupAdd({ defaultExecution, defaultControlPlane }) {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<ContainerGroupForm
|
<ContainerGroupForm
|
||||||
defaultControlPlane={defaultControlPlane}
|
|
||||||
defaultExecution={defaultExecution}
|
|
||||||
initialPodSpec={initialPodSpec}
|
initialPodSpec={initialPodSpec}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
submitError={submitError}
|
submitError={submitError}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { jsonToYaml, isJsonString } from 'util/yaml';
|
|||||||
import { InstanceGroupsAPI } from 'api';
|
import { InstanceGroupsAPI } from 'api';
|
||||||
import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
|
import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
|
||||||
|
|
||||||
function ContainerGroupDetails({ instanceGroup, defaultExecution }) {
|
function ContainerGroupDetails({ instanceGroup }) {
|
||||||
const { id, name } = instanceGroup;
|
const { id, name } = instanceGroup;
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -99,8 +99,7 @@ function ContainerGroupDetails({ instanceGroup, defaultExecution }) {
|
|||||||
{t`Edit`}
|
{t`Edit`}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{name !== defaultExecution &&
|
{instanceGroup.summary_fields.user_capabilities &&
|
||||||
instanceGroup.summary_fields.user_capabilities &&
|
|
||||||
instanceGroup.summary_fields.user_capabilities.delete && (
|
instanceGroup.summary_fields.user_capabilities.delete && (
|
||||||
<DeleteButton
|
<DeleteButton
|
||||||
ouiaId="container-group-detail-delete-button"
|
ouiaId="container-group-detail-delete-button"
|
||||||
|
|||||||
@@ -9,11 +9,7 @@ import ContentError from 'components/ContentError';
|
|||||||
import ContentLoading from 'components/ContentLoading';
|
import ContentLoading from 'components/ContentLoading';
|
||||||
import ContainerGroupForm from '../shared/ContainerGroupForm';
|
import ContainerGroupForm from '../shared/ContainerGroupForm';
|
||||||
|
|
||||||
function ContainerGroupEdit({
|
function ContainerGroupEdit({ instanceGroup }) {
|
||||||
instanceGroup,
|
|
||||||
defaultControlPlane,
|
|
||||||
defaultExecution,
|
|
||||||
}) {
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [submitError, setSubmitError] = useState(null);
|
const [submitError, setSubmitError] = useState(null);
|
||||||
const detailsIUrl = `/instance_groups/container_group/${instanceGroup.id}/details`;
|
const detailsIUrl = `/instance_groups/container_group/${instanceGroup.id}/details`;
|
||||||
@@ -77,8 +73,6 @@ function ContainerGroupEdit({
|
|||||||
return (
|
return (
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<ContainerGroupForm
|
<ContainerGroupForm
|
||||||
defaultControlPlane={defaultControlPlane}
|
|
||||||
defaultExecution={defaultExecution}
|
|
||||||
instanceGroup={instanceGroup}
|
instanceGroup={instanceGroup}
|
||||||
initialPodSpec={initialPodSpec}
|
initialPodSpec={initialPodSpec}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { CaretLeftIcon } from '@patternfly/react-icons';
|
|||||||
import { Card, PageSection } from '@patternfly/react-core';
|
import { Card, PageSection } from '@patternfly/react-core';
|
||||||
|
|
||||||
import useRequest from 'hooks/useRequest';
|
import useRequest from 'hooks/useRequest';
|
||||||
import { InstanceGroupsAPI, SettingsAPI } from 'api';
|
import { InstanceGroupsAPI } from 'api';
|
||||||
import RoutedTabs from 'components/RoutedTabs';
|
import RoutedTabs from 'components/RoutedTabs';
|
||||||
import ContentError from 'components/ContentError';
|
import ContentError from 'components/ContentError';
|
||||||
import ContentLoading from 'components/ContentLoading';
|
import ContentLoading from 'components/ContentLoading';
|
||||||
@@ -31,29 +31,16 @@ function InstanceGroup({ setBreadcrumb }) {
|
|||||||
isLoading,
|
isLoading,
|
||||||
error: contentError,
|
error: contentError,
|
||||||
request: fetchInstanceGroups,
|
request: fetchInstanceGroups,
|
||||||
result: { instanceGroup, defaultControlPlane, defaultExecution },
|
result: { instanceGroup },
|
||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const [
|
const { data } = await InstanceGroupsAPI.readDetail(id);
|
||||||
{ data },
|
|
||||||
{
|
|
||||||
data: {
|
|
||||||
DEFAULT_CONTROL_PLANE_QUEUE_NAME,
|
|
||||||
DEFAULT_EXECUTION_QUEUE_NAME,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] = await Promise.all([
|
|
||||||
InstanceGroupsAPI.readDetail(id),
|
|
||||||
SettingsAPI.readAll(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
instanceGroup: data,
|
instanceGroup: data,
|
||||||
defaultControlPlane: DEFAULT_CONTROL_PLANE_QUEUE_NAME,
|
|
||||||
defaultExecution: DEFAULT_EXECUTION_QUEUE_NAME,
|
|
||||||
};
|
};
|
||||||
}, [id]),
|
}, [id]),
|
||||||
{ instanceGroup: null, defaultControlPlane: '', defaultExecution: '' }
|
{ instanceGroup: null }
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -133,18 +120,10 @@ function InstanceGroup({ setBreadcrumb }) {
|
|||||||
{instanceGroup && (
|
{instanceGroup && (
|
||||||
<>
|
<>
|
||||||
<Route path="/instance_groups/:id/edit">
|
<Route path="/instance_groups/:id/edit">
|
||||||
<InstanceGroupEdit
|
<InstanceGroupEdit instanceGroup={instanceGroup} />
|
||||||
instanceGroup={instanceGroup}
|
|
||||||
defaultExecution={defaultExecution}
|
|
||||||
defaultControlPlane={defaultControlPlane}
|
|
||||||
/>
|
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/instance_groups/:id/details">
|
<Route path="/instance_groups/:id/details">
|
||||||
<InstanceGroupDetails
|
<InstanceGroupDetails instanceGroup={instanceGroup} />
|
||||||
defaultExecution={defaultExecution}
|
|
||||||
defaultControlPlane={defaultControlPlane}
|
|
||||||
instanceGroup={instanceGroup}
|
|
||||||
/>
|
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/instance_groups/:id/instances">
|
<Route path="/instance_groups/:id/instances">
|
||||||
<Instances
|
<Instances
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { CardBody } from 'components/Card';
|
|||||||
import { InstanceGroupsAPI } from 'api';
|
import { InstanceGroupsAPI } from 'api';
|
||||||
import InstanceGroupForm from '../shared/InstanceGroupForm';
|
import InstanceGroupForm from '../shared/InstanceGroupForm';
|
||||||
|
|
||||||
function InstanceGroupAdd({ defaultExecution, defaultControlPlane }) {
|
function InstanceGroupAdd() {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [submitError, setSubmitError] = useState(null);
|
const [submitError, setSubmitError] = useState(null);
|
||||||
|
|
||||||
@@ -28,8 +28,6 @@ function InstanceGroupAdd({ defaultExecution, defaultControlPlane }) {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<InstanceGroupForm
|
<InstanceGroupForm
|
||||||
defaultControlPlane={defaultControlPlane}
|
|
||||||
defaultExecution={defaultExecution}
|
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
submitError={submitError}
|
submitError={submitError}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
|
|||||||
@@ -23,11 +23,7 @@ const Unavailable = styled.span`
|
|||||||
color: var(--pf-global--danger-color--200);
|
color: var(--pf-global--danger-color--200);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function InstanceGroupDetails({
|
function InstanceGroupDetails({ instanceGroup }) {
|
||||||
instanceGroup,
|
|
||||||
defaultControlPlane,
|
|
||||||
defaultExecution,
|
|
||||||
}) {
|
|
||||||
const { id, name } = instanceGroup;
|
const { id, name } = instanceGroup;
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -46,8 +42,6 @@ function InstanceGroupDetails({
|
|||||||
const { error, dismissError } = useDismissableError(deleteError);
|
const { error, dismissError } = useDismissableError(deleteError);
|
||||||
const deleteDetailsRequests =
|
const deleteDetailsRequests =
|
||||||
relatedResourceDeleteRequests.instanceGroup(instanceGroup);
|
relatedResourceDeleteRequests.instanceGroup(instanceGroup);
|
||||||
const isDefaultInstanceGroup =
|
|
||||||
name === defaultControlPlane || name === defaultExecution;
|
|
||||||
return (
|
return (
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<DetailList>
|
<DetailList>
|
||||||
@@ -115,8 +109,7 @@ function InstanceGroupDetails({
|
|||||||
{t`Edit`}
|
{t`Edit`}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!isDefaultInstanceGroup &&
|
{instanceGroup.summary_fields.user_capabilities &&
|
||||||
instanceGroup.summary_fields.user_capabilities &&
|
|
||||||
instanceGroup.summary_fields.user_capabilities.delete && (
|
instanceGroup.summary_fields.user_capabilities.delete && (
|
||||||
<DeleteButton
|
<DeleteButton
|
||||||
ouiaId="instance-group-detail-delete-button"
|
ouiaId="instance-group-detail-delete-button"
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ import { CardBody } from 'components/Card';
|
|||||||
import { InstanceGroupsAPI } from 'api';
|
import { InstanceGroupsAPI } from 'api';
|
||||||
import InstanceGroupForm from '../shared/InstanceGroupForm';
|
import InstanceGroupForm from '../shared/InstanceGroupForm';
|
||||||
|
|
||||||
function InstanceGroupEdit({
|
function InstanceGroupEdit({ instanceGroup }) {
|
||||||
instanceGroup,
|
|
||||||
defaultControlPlane,
|
|
||||||
defaultExecution,
|
|
||||||
}) {
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [submitError, setSubmitError] = useState(null);
|
const [submitError, setSubmitError] = useState(null);
|
||||||
const detailsUrl = `/instance_groups/${instanceGroup.id}/details`;
|
const detailsUrl = `/instance_groups/${instanceGroup.id}/details`;
|
||||||
@@ -31,8 +27,6 @@ function InstanceGroupEdit({
|
|||||||
<CardBody>
|
<CardBody>
|
||||||
<InstanceGroupForm
|
<InstanceGroupForm
|
||||||
instanceGroup={instanceGroup}
|
instanceGroup={instanceGroup}
|
||||||
defaultControlPlane={defaultControlPlane}
|
|
||||||
defaultExecution={defaultExecution}
|
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
submitError={submitError}
|
submitError={submitError}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
|
|||||||
@@ -55,10 +55,7 @@ describe('<InstanceGroupEdit>', () => {
|
|||||||
history = createMemoryHistory();
|
history = createMemoryHistory();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<InstanceGroupEdit
|
<InstanceGroupEdit instanceGroup={instanceGroupData} />,
|
||||||
defaultControlPlane="controlplane"
|
|
||||||
instanceGroup={instanceGroupData}
|
|
||||||
/>,
|
|
||||||
{
|
{
|
||||||
context: { router: { history } },
|
context: { router: { history } },
|
||||||
}
|
}
|
||||||
@@ -70,27 +67,6 @@ describe('<InstanceGroupEdit>', () => {
|
|||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('controlplane instance group name can not be updated', async () => {
|
|
||||||
let towerWrapper;
|
|
||||||
await act(async () => {
|
|
||||||
towerWrapper = mountWithContexts(
|
|
||||||
<InstanceGroupEdit
|
|
||||||
defaultControlPlane="controlplane"
|
|
||||||
instanceGroup={{ ...instanceGroupData, name: 'controlplane' }}
|
|
||||||
/>,
|
|
||||||
{
|
|
||||||
context: { router: { history } },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
towerWrapper.find('input#instance-group-name').prop('disabled')
|
|
||||||
).toBeTruthy();
|
|
||||||
expect(
|
|
||||||
towerWrapper.find('input#instance-group-name').prop('value')
|
|
||||||
).toEqual('controlplane');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('handleSubmit should call the api and redirect to details page', async () => {
|
test('handleSubmit should call the api and redirect to details page', async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper.find('InstanceGroupForm').invoke('onSubmit')(
|
wrapper.find('InstanceGroupForm').invoke('onSubmit')(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useLocation, useRouteMatch, Link } from 'react-router-dom';
|
|||||||
import { t, Plural } from '@lingui/macro';
|
import { t, Plural } from '@lingui/macro';
|
||||||
import { Card, PageSection, DropdownItem } from '@patternfly/react-core';
|
import { Card, PageSection, DropdownItem } from '@patternfly/react-core';
|
||||||
|
|
||||||
import { InstanceGroupsAPI, SettingsAPI } from 'api';
|
import { InstanceGroupsAPI } from 'api';
|
||||||
import { getQSConfig, parseQueryString } from 'util/qs';
|
import { getQSConfig, parseQueryString } from 'util/qs';
|
||||||
import useRequest, { useDeleteItems } from 'hooks/useRequest';
|
import useRequest, { useDeleteItems } from 'hooks/useRequest';
|
||||||
import useSelected from 'hooks/useSelected';
|
import useSelected from 'hooks/useSelected';
|
||||||
@@ -27,28 +27,6 @@ const QS_CONFIG = getQSConfig('instance-group', {
|
|||||||
page_size: 20,
|
page_size: 20,
|
||||||
});
|
});
|
||||||
|
|
||||||
function modifyInstanceGroups(
|
|
||||||
defaultControlPlane,
|
|
||||||
defaultExecution,
|
|
||||||
items = []
|
|
||||||
) {
|
|
||||||
return items.map((item) => {
|
|
||||||
const clonedItem = {
|
|
||||||
...item,
|
|
||||||
summary_fields: {
|
|
||||||
...item.summary_fields,
|
|
||||||
user_capabilities: {
|
|
||||||
...item.summary_fields.user_capabilities,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (clonedItem.name === (defaultControlPlane || defaultExecution)) {
|
|
||||||
clonedItem.summary_fields.user_capabilities.delete = false;
|
|
||||||
}
|
|
||||||
return clonedItem;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function InstanceGroupList({
|
function InstanceGroupList({
|
||||||
isKubernetes,
|
isKubernetes,
|
||||||
isSettingsRequestLoading,
|
isSettingsRequestLoading,
|
||||||
@@ -56,30 +34,6 @@ function InstanceGroupList({
|
|||||||
}) {
|
}) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const match = useRouteMatch();
|
const match = useRouteMatch();
|
||||||
const {
|
|
||||||
error: protectedItemsError,
|
|
||||||
isLoading: isLoadingProtectedItems,
|
|
||||||
request: fetchProtectedItems,
|
|
||||||
result: { defaultControlPlane, defaultExecution },
|
|
||||||
} = useRequest(
|
|
||||||
useCallback(async () => {
|
|
||||||
const {
|
|
||||||
data: {
|
|
||||||
DEFAULT_CONTROL_PLANE_QUEUE_NAME,
|
|
||||||
DEFAULT_EXECUTION_QUEUE_NAME,
|
|
||||||
},
|
|
||||||
} = await SettingsAPI.readAll();
|
|
||||||
return {
|
|
||||||
defaultControlPlane: DEFAULT_CONTROL_PLANE_QUEUE_NAME,
|
|
||||||
defaultExecution: DEFAULT_EXECUTION_QUEUE_NAME,
|
|
||||||
};
|
|
||||||
}, []),
|
|
||||||
{ defaultControlPlane: '', defaultExecution: '' }
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchProtectedItems();
|
|
||||||
}, [fetchProtectedItems]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
error: contentError,
|
error: contentError,
|
||||||
@@ -127,12 +81,6 @@ function InstanceGroupList({
|
|||||||
const { selected, isAllSelected, handleSelect, clearSelected, selectAll } =
|
const { selected, isAllSelected, handleSelect, clearSelected, selectAll } =
|
||||||
useSelected(instanceGroups);
|
useSelected(instanceGroups);
|
||||||
|
|
||||||
const modifiedSelected = modifyInstanceGroups(
|
|
||||||
defaultControlPlane,
|
|
||||||
defaultExecution,
|
|
||||||
selected
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isLoading: deleteLoading,
|
isLoading: deleteLoading,
|
||||||
deletionError,
|
deletionError,
|
||||||
@@ -158,28 +106,10 @@ function InstanceGroupList({
|
|||||||
|
|
||||||
const canAdd = actions && actions.POST;
|
const canAdd = actions && actions.POST;
|
||||||
|
|
||||||
const cannotDelete = (item) =>
|
const cannotDelete = (item) => !item.summary_fields.user_capabilities.delete;
|
||||||
!item.summary_fields.user_capabilities.delete ||
|
|
||||||
item.name === defaultExecution ||
|
|
||||||
item.name === defaultControlPlane;
|
|
||||||
|
|
||||||
const pluralizedItemName = t`Instance Groups`;
|
const pluralizedItemName = t`Instance Groups`;
|
||||||
|
|
||||||
let errorMessageDelete = '';
|
|
||||||
const notdeletedable = selected.filter(
|
|
||||||
(i) => i.name === defaultControlPlane || i.name === defaultExecution
|
|
||||||
);
|
|
||||||
|
|
||||||
if (notdeletedable.length) {
|
|
||||||
errorMessageDelete = (
|
|
||||||
<Plural
|
|
||||||
value={notdeletedable.length}
|
|
||||||
one="The following Instance Group cannot be deleted"
|
|
||||||
other="The following Instance Groups cannot be deleted"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const addContainerGroup = t`Add container group`;
|
const addContainerGroup = t`Add container group`;
|
||||||
const addInstanceGroup = t`Add instance group`;
|
const addInstanceGroup = t`Add instance group`;
|
||||||
|
|
||||||
@@ -229,14 +159,9 @@ function InstanceGroupList({
|
|||||||
<PageSection>
|
<PageSection>
|
||||||
<Card>
|
<Card>
|
||||||
<PaginatedTable
|
<PaginatedTable
|
||||||
contentError={
|
contentError={contentError || settingsRequestError}
|
||||||
contentError || settingsRequestError || protectedItemsError
|
|
||||||
}
|
|
||||||
hasContentLoading={
|
hasContentLoading={
|
||||||
isLoading ||
|
isLoading || deleteLoading || isSettingsRequestLoading
|
||||||
deleteLoading ||
|
|
||||||
isSettingsRequestLoading ||
|
|
||||||
isLoadingProtectedItems
|
|
||||||
}
|
}
|
||||||
items={instanceGroups}
|
items={instanceGroups}
|
||||||
itemCount={instanceGroupsCount}
|
itemCount={instanceGroupsCount}
|
||||||
@@ -264,9 +189,8 @@ function InstanceGroupList({
|
|||||||
key="delete"
|
key="delete"
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
cannotDelete={cannotDelete}
|
cannotDelete={cannotDelete}
|
||||||
itemsToDelete={modifiedSelected}
|
itemsToDelete={selected}
|
||||||
pluralizedItemName={t`Instance Groups`}
|
pluralizedItemName={t`Instance Groups`}
|
||||||
errorMessage={errorMessageDelete}
|
|
||||||
deleteDetailsRequests={deleteDetailsRequests}
|
deleteDetailsRequests={deleteDetailsRequests}
|
||||||
deleteMessage={
|
deleteMessage={
|
||||||
<Plural
|
<Plural
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { t } from '@lingui/macro';
|
|||||||
import { Route, Switch, useLocation } from 'react-router-dom';
|
import { Route, Switch, useLocation } from 'react-router-dom';
|
||||||
import { Card, PageSection } from '@patternfly/react-core';
|
import { Card, PageSection } from '@patternfly/react-core';
|
||||||
|
|
||||||
|
import { useUserProfile } from 'contexts/Config';
|
||||||
import useRequest from 'hooks/useRequest';
|
import useRequest from 'hooks/useRequest';
|
||||||
import { SettingsAPI } from 'api';
|
import { SettingsAPI } from 'api';
|
||||||
import ScreenHeader from 'components/ScreenHeader';
|
import ScreenHeader from 'components/ScreenHeader';
|
||||||
@@ -16,31 +17,28 @@ import ContainerGroup from './ContainerGroup';
|
|||||||
|
|
||||||
function InstanceGroups() {
|
function InstanceGroups() {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
const { isSuperUser, isSystemAuditor } = useUserProfile();
|
||||||
|
const userCanReadSettings = isSuperUser || isSystemAuditor;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
request: settingsRequest,
|
request: settingsRequest,
|
||||||
isLoading: isSettingsRequestLoading,
|
isLoading: isSettingsRequestLoading,
|
||||||
error: settingsRequestError,
|
error: settingsRequestError,
|
||||||
result: { isKubernetes, defaultControlPlane, defaultExecution },
|
result: { isKubernetes },
|
||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const {
|
const {
|
||||||
data: {
|
data: { IS_K8S },
|
||||||
IS_K8S,
|
|
||||||
DEFAULT_CONTROL_PLANE_QUEUE_NAME,
|
|
||||||
DEFAULT_EXECUTION_QUEUE_NAME,
|
|
||||||
},
|
|
||||||
} = await SettingsAPI.readCategory('all');
|
} = await SettingsAPI.readCategory('all');
|
||||||
return {
|
return {
|
||||||
isKubernetes: IS_K8S,
|
isKubernetes: IS_K8S,
|
||||||
defaultControlPlane: DEFAULT_CONTROL_PLANE_QUEUE_NAME,
|
|
||||||
defaultExecution: DEFAULT_EXECUTION_QUEUE_NAME,
|
|
||||||
};
|
};
|
||||||
}, []),
|
}, []),
|
||||||
{ isLoading: true }
|
{ isKubernetes: false }
|
||||||
);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
settingsRequest();
|
userCanReadSettings && settingsRequest();
|
||||||
}, [settingsRequest]);
|
}, [settingsRequest, userCanReadSettings]);
|
||||||
|
|
||||||
const [breadcrumbConfig, setBreadcrumbConfig] = useState({
|
const [breadcrumbConfig, setBreadcrumbConfig] = useState({
|
||||||
'/instance_groups': t`Instance Groups`,
|
'/instance_groups': t`Instance Groups`,
|
||||||
@@ -91,20 +89,14 @@ function InstanceGroups() {
|
|||||||
) : (
|
) : (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/instance_groups/container_group/add">
|
<Route path="/instance_groups/container_group/add">
|
||||||
<ContainerGroupAdd
|
<ContainerGroupAdd />
|
||||||
defaultControlPlane={defaultControlPlane}
|
|
||||||
defaultExecution={defaultExecution}
|
|
||||||
/>
|
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/instance_groups/container_group/:id">
|
<Route path="/instance_groups/container_group/:id">
|
||||||
<ContainerGroup setBreadcrumb={buildBreadcrumbConfig} />
|
<ContainerGroup setBreadcrumb={buildBreadcrumbConfig} />
|
||||||
</Route>
|
</Route>
|
||||||
{!isKubernetes && (
|
{!isKubernetes && (
|
||||||
<Route path="/instance_groups/add">
|
<Route path="/instance_groups/add">
|
||||||
<InstanceGroupAdd
|
<InstanceGroupAdd />
|
||||||
defaultControlPlane={defaultControlPlane}
|
|
||||||
defaultExecution={defaultExecution}
|
|
||||||
/>
|
|
||||||
</Route>
|
</Route>
|
||||||
)}
|
)}
|
||||||
<Route path="/instance_groups/:id">
|
<Route path="/instance_groups/:id">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import { InstanceGroupsAPI } from 'api';
|
import { InstanceGroupsAPI } from 'api';
|
||||||
import InstanceGroups from './InstanceGroups';
|
import InstanceGroups from './InstanceGroups';
|
||||||
|
import { useUserProfile } from 'contexts/Config';
|
||||||
|
|
||||||
const mockUseLocationValue = {
|
const mockUseLocationValue = {
|
||||||
pathname: '',
|
pathname: '',
|
||||||
@@ -11,6 +12,19 @@ jest.mock('react-router-dom', () => ({
|
|||||||
...jest.requireActual('react-router-dom'),
|
...jest.requireActual('react-router-dom'),
|
||||||
useLocation: () => mockUseLocationValue,
|
useLocation: () => mockUseLocationValue,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
useUserProfile.mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
isSuperUser: true,
|
||||||
|
isSystemAuditor: false,
|
||||||
|
isOrgAdmin: false,
|
||||||
|
isNotificationAdmin: false,
|
||||||
|
isExecEnvAdmin: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('<InstanceGroups/>', () => {
|
describe('<InstanceGroups/>', () => {
|
||||||
test('should set breadcrumbs', () => {
|
test('should set breadcrumbs', () => {
|
||||||
mockUseLocationValue.pathname = '/instance_groups';
|
mockUseLocationValue.pathname = '/instance_groups';
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import FormField, {
|
|||||||
CheckboxField,
|
CheckboxField,
|
||||||
} from 'components/FormField';
|
} from 'components/FormField';
|
||||||
import FormActionGroup from 'components/FormActionGroup';
|
import FormActionGroup from 'components/FormActionGroup';
|
||||||
import { combine, required, protectedResourceName } from 'util/validators';
|
import { required } from 'util/validators';
|
||||||
import {
|
import {
|
||||||
FormColumnLayout,
|
FormColumnLayout,
|
||||||
FormFullWidthLayout,
|
FormFullWidthLayout,
|
||||||
@@ -21,21 +21,11 @@ import {
|
|||||||
import CredentialLookup from 'components/Lookup/CredentialLookup';
|
import CredentialLookup from 'components/Lookup/CredentialLookup';
|
||||||
import { VariablesField } from 'components/CodeEditor';
|
import { VariablesField } from 'components/CodeEditor';
|
||||||
|
|
||||||
function ContainerGroupFormFields({
|
function ContainerGroupFormFields({ instanceGroup }) {
|
||||||
instanceGroup,
|
|
||||||
defaultControlPlane,
|
|
||||||
defaultExecution,
|
|
||||||
}) {
|
|
||||||
const { setFieldValue, setFieldTouched } = useFormikContext();
|
const { setFieldValue, setFieldTouched } = useFormikContext();
|
||||||
const [credentialField, credentialMeta, credentialHelpers] =
|
const [credentialField, credentialMeta, credentialHelpers] =
|
||||||
useField('credential');
|
useField('credential');
|
||||||
|
|
||||||
const [, { initialValue }] = useField('name');
|
|
||||||
|
|
||||||
const isProtected =
|
|
||||||
initialValue === `${defaultControlPlane}` ||
|
|
||||||
initialValue === `${defaultExecution}`;
|
|
||||||
|
|
||||||
const [overrideField] = useField('override');
|
const [overrideField] = useField('override');
|
||||||
|
|
||||||
const handleCredentialUpdate = useCallback(
|
const handleCredentialUpdate = useCallback(
|
||||||
@@ -50,21 +40,10 @@ function ContainerGroupFormFields({
|
|||||||
<>
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
name="name"
|
name="name"
|
||||||
helperText={
|
|
||||||
isProtected
|
|
||||||
? t`This is a protected Instance Group. The name cannot be changed.`
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
id="container-group-name"
|
id="container-group-name"
|
||||||
label={t`Name`}
|
label={t`Name`}
|
||||||
type="text"
|
type="text"
|
||||||
validate={combine([
|
validate={required(null)}
|
||||||
required(null),
|
|
||||||
protectedResourceName(
|
|
||||||
t`This is a protected name for Container Groups. Please use a different name.`,
|
|
||||||
[defaultControlPlane, defaultExecution]
|
|
||||||
),
|
|
||||||
])}
|
|
||||||
isRequired
|
isRequired
|
||||||
/>
|
/>
|
||||||
<CredentialLookup
|
<CredentialLookup
|
||||||
|
|||||||
@@ -1,49 +1,25 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { func, shape } from 'prop-types';
|
import { func, shape } from 'prop-types';
|
||||||
import { Formik, useField } from 'formik';
|
import { Formik } from 'formik';
|
||||||
|
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Form } from '@patternfly/react-core';
|
import { Form } from '@patternfly/react-core';
|
||||||
|
|
||||||
import FormField, { FormSubmitError } from 'components/FormField';
|
import FormField, { FormSubmitError } from 'components/FormField';
|
||||||
import FormActionGroup from 'components/FormActionGroup';
|
import FormActionGroup from 'components/FormActionGroup';
|
||||||
import {
|
import { required, minMaxValue } from 'util/validators';
|
||||||
combine,
|
|
||||||
required,
|
|
||||||
protectedResourceName,
|
|
||||||
minMaxValue,
|
|
||||||
} from 'util/validators';
|
|
||||||
import { FormColumnLayout } from 'components/FormLayout';
|
import { FormColumnLayout } from 'components/FormLayout';
|
||||||
|
|
||||||
function InstanceGroupFormFields({ defaultControlPlane, defaultExecution }) {
|
function InstanceGroupFormFields() {
|
||||||
const [, { initialValue }] = useField('name');
|
|
||||||
const isProtected =
|
|
||||||
initialValue === `${defaultControlPlane}` ||
|
|
||||||
initialValue === `${defaultExecution}`;
|
|
||||||
|
|
||||||
const validators = combine([
|
|
||||||
required(null),
|
|
||||||
protectedResourceName(
|
|
||||||
t`This is a protected name for Instance Groups. Please use a different name.`,
|
|
||||||
[defaultControlPlane, defaultExecution]
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
name="name"
|
name="name"
|
||||||
helperText={
|
|
||||||
isProtected
|
|
||||||
? t`This is a protected Instance Group. The name cannot be changed.`
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
id="instance-group-name"
|
id="instance-group-name"
|
||||||
label={t`Name`}
|
label={t`Name`}
|
||||||
type="text"
|
type="text"
|
||||||
validate={validators}
|
validate={required(null)}
|
||||||
isRequired
|
isRequired
|
||||||
isDisabled={isProtected}
|
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
id="instance-group-policy-instance-minimum"
|
id="instance-group-policy-instance-minimum"
|
||||||
|
|||||||
@@ -117,44 +117,4 @@ describe('<InstanceGroupForm/>', () => {
|
|||||||
wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
|
wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
|
||||||
expect(onCancel).toBeCalled();
|
expect(onCancel).toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Name field should be disabled, default', async () => {
|
|
||||||
let defaultInstanceGroupWrapper;
|
|
||||||
await act(async () => {
|
|
||||||
defaultInstanceGroupWrapper = mountWithContexts(
|
|
||||||
<InstanceGroupForm
|
|
||||||
onCancel={onCancel}
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
defaultControlPlane="controlplane"
|
|
||||||
defaultExecution="default"
|
|
||||||
instanceGroup={{ ...instanceGroup, name: 'default' }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
defaultInstanceGroupWrapper
|
|
||||||
.find('TextInput[name="name"]')
|
|
||||||
.prop('isDisabled')
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Name field should be disabled, controlplane', async () => {
|
|
||||||
let defaultInstanceGroupWrapper;
|
|
||||||
await act(async () => {
|
|
||||||
defaultInstanceGroupWrapper = mountWithContexts(
|
|
||||||
<InstanceGroupForm
|
|
||||||
onCancel={onCancel}
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
defaultControlPlane="controlplane"
|
|
||||||
defaultExecution="default"
|
|
||||||
instanceGroup={{ ...instanceGroup, name: 'controlplane' }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
defaultInstanceGroupWrapper
|
|
||||||
.find('TextInput[name="name"]')
|
|
||||||
.prop('isDisabled')
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ jest.doMock('./contexts/Config', () => ({
|
|||||||
Config: MockConfigContext.Consumer,
|
Config: MockConfigContext.Consumer,
|
||||||
useConfig: () => React.useContext(MockConfigContext),
|
useConfig: () => React.useContext(MockConfigContext),
|
||||||
useAuthorizedPath: jest.fn(),
|
useAuthorizedPath: jest.fn(),
|
||||||
|
useUserProfile: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// ?
|
// ?
|
||||||
|
|||||||
Reference in New Issue
Block a user