@@ -255,7 +257,11 @@ const FrequencyDetailSubform = ({ i18n }) => {
fieldId="schedule-days-of-week"
helperTextInvalid={daysOfWeekMeta.error}
isRequired
- isValid={!daysOfWeekMeta.touched || !daysOfWeekMeta.error}
+ validated={
+ !daysOfWeekMeta.touched || !daysOfWeekMeta.error
+ ? 'default'
+ : 'error'
+ }
label={i18n._(t`On days`)}
>
@@ -339,7 +345,9 @@ const FrequencyDetailSubform = ({ i18n }) => {
fieldId="schedule-run-on"
helperTextInvalid={runOnMeta.error}
isRequired
- isValid={!runOnMeta.touched || !runOnMeta.error}
+ validated={
+ !runOnMeta.touched || !runOnMeta.error ? 'default' : 'error'
+ }
label={i18n._(t`Run on`)}
>
{
fieldId="schedule-end"
helperTextInvalid={endMeta.error}
isRequired
- isValid={!endMeta.touched || !endMeta.error}
+ validated={!endMeta.touched || !endMeta.error ? 'default' : 'error'}
label={i18n._(t`End`)}
>
{
fieldId="schedule-end-datetime"
helperTextInvalid={endDateTimeMeta.error}
isRequired
- isValid={!endDateTimeMeta.touched || !endDateTimeMeta.error}
+ validated={
+ !endDateTimeMeta.touched || !endDateTimeMeta.error
+ ? 'default'
+ : 'error'
+ }
label={i18n._(t`End date/time`)}
>
{frequency.value !== 'none' && (
- {i18n._(t`Frequency Details`)}
+
+ {i18n._(t`Frequency Details`)}
+
diff --git a/awx/ui_next/src/components/Search/Search.jsx b/awx/ui_next/src/components/Search/Search.jsx
index 91c3987cea..0fb59e0806 100644
--- a/awx/ui_next/src/components/Search/Search.jsx
+++ b/awx/ui_next/src/components/Search/Search.jsx
@@ -16,12 +16,10 @@ import {
SelectOption,
SelectVariant,
TextInput,
+ ToolbarGroup,
+ ToolbarItem,
+ ToolbarFilter,
} from '@patternfly/react-core';
-import {
- DataToolbarGroup,
- DataToolbarItem,
- DataToolbarFilter,
-} from '@patternfly/react-core/dist/umd/experimental';
import { SearchIcon } from '@patternfly/react-icons';
import styled from 'styled-components';
import { parseQueryString } from '../../util/qs';
@@ -205,8 +203,8 @@ class Search extends React.Component {
const chipsByKey = getChipsByKey();
return (
-
-
+
+
{searchDropdownItems.length > 0 ? (
{searchColumnName}
)}
-
+
{columns.map(
({ key, name, options, isBoolean, booleanLabels = {} }) => (
- {
const [columnKey, ...value] = chip.key.split(':');
@@ -253,7 +251,7 @@ class Search extends React.Component {
const [, ...value] = chip.key.split(':');
return value.join(':');
})}
- isExpanded={isFilterDropdownOpen}
+ isOpen={isFilterDropdownOpen}
placeholderText={`Filter By ${name}`}
>
{options.map(([optionKey, optionLabel]) => (
@@ -272,7 +270,7 @@ class Search extends React.Component {
this.handleFilterBooleanSelect(key, selection)
}
selections={chipsByKey[key].chips[0]}
- isExpanded={isFilterDropdownOpen}
+ isOpen={isFilterDropdownOpen}
placeholderText={`Filter By ${name}`}
>
@@ -311,10 +309,10 @@ class Search extends React.Component {
)}
-
+
)
)}
-
+
);
}
}
diff --git a/awx/ui_next/src/components/Search/Search.test.jsx b/awx/ui_next/src/components/Search/Search.test.jsx
index 503fa9ec58..5eac1c532b 100644
--- a/awx/ui_next/src/components/Search/Search.test.jsx
+++ b/awx/ui_next/src/components/Search/Search.test.jsx
@@ -1,8 +1,5 @@
import React from 'react';
-import {
- DataToolbar,
- DataToolbarContent,
-} from '@patternfly/react-core/dist/umd/experimental';
+import { Toolbar, ToolbarContent } from '@patternfly/react-core';
import { createMemoryHistory } from 'history';
import { act } from 'react-dom/test-utils';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
@@ -33,15 +30,15 @@ describe('
', () => {
const onSearch = jest.fn();
search = mountWithContexts(
-
{}}
- collapseListedFiltersBreakpoint="md"
+ collapseListedFiltersBreakpoint="lg"
>
-
+
-
-
+
+
);
search.find(searchTextInput).instance().value = 'test-321';
@@ -56,15 +53,15 @@ describe('
', () => {
const columns = [{ name: 'Name', key: 'name', isDefault: true }];
const onSearch = jest.fn();
const wrapper = mountWithContexts(
-
{}}
- collapseListedFiltersBreakpoint="md"
+ collapseListedFiltersBreakpoint="lg"
>
-
+
-
-
+
+
).find('Search');
expect(wrapper.state('isSearchDropdownOpen')).toEqual(false);
wrapper.instance().handleDropdownToggle(true);
@@ -78,15 +75,15 @@ describe('
', () => {
];
const onSearch = jest.fn();
const wrapper = mountWithContexts(
-
{}}
- collapseListedFiltersBreakpoint="md"
+ collapseListedFiltersBreakpoint="lg"
>
-
+
-
-
+
+
).find('Search');
expect(wrapper.state('searchKey')).toEqual('name');
wrapper
@@ -101,15 +98,15 @@ describe('
', () => {
const columns = [{ name: 'Name', key: 'name', isDefault: true }];
const onSearch = jest.fn();
const wrapper = mountWithContexts(
-
{}}
- collapseListedFiltersBreakpoint="md"
+ collapseListedFiltersBreakpoint="lg"
>
-
+
-
-
+
+
);
wrapper.find(searchTextInput).instance().value = '';
@@ -125,15 +122,15 @@ describe('
', () => {
const columns = [{ name: 'Name', key: 'name', isDefault: true }];
const onSearch = jest.fn();
const wrapper = mountWithContexts(
-
{}}
- collapseListedFiltersBreakpoint="md"
+ collapseListedFiltersBreakpoint="lg"
>
-
+
-
-
+
+
);
wrapper.find(searchTextInput).instance().value = 'test-321';
@@ -156,23 +153,23 @@ describe('
', () => {
initialEntries: [`/organizations/${query}`],
});
const wrapper = mountWithContexts(
-
{}}
- collapseListedFiltersBreakpoint="md"
+ collapseListedFiltersBreakpoint="lg"
>
-
+
-
- ,
+
+ ,
{ context: { router: { history } } }
);
const typeFilterWrapper = wrapper.find(
- 'DataToolbarFilter[categoryName="Type"]'
+ 'ToolbarFilter[categoryName="Type"]'
);
expect(typeFilterWrapper.prop('chips')[0].key).toEqual('or__type:foo');
const nameFilterWrapper = wrapper.find(
- 'DataToolbarFilter[categoryName="Name"]'
+ 'ToolbarFilter[categoryName="Name"]'
);
expect(nameFilterWrapper.prop('chips')[0].key).toEqual('name:bar');
});
@@ -197,19 +194,19 @@ describe('
', () => {
});
const onRemove = jest.fn();
const wrapper = mountWithContexts(
-
{}}
- collapseListedFiltersBreakpoint="md"
+ collapseListedFiltersBreakpoint="lg"
>
-
+
-
- ,
+
+ ,
{ context: { router: { history } } }
);
expect(history.location.search).toEqual(query);
@@ -243,19 +240,19 @@ describe('
', () => {
});
const onRemove = jest.fn();
const wrapper = mountWithContexts(
-
{}}
- collapseListedFiltersBreakpoint="md"
+ collapseListedFiltersBreakpoint="lg"
>
-
+
-
- ,
+
+ ,
{ context: { router: { history } } }
);
expect(history.location.search).toEqual(query);
diff --git a/awx/ui_next/src/components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.test.jsx b/awx/ui_next/src/components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.test.jsx
index b80c24a493..b5f986c5db 100644
--- a/awx/ui_next/src/components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.test.jsx
+++ b/awx/ui_next/src/components/UserAndTeamAccessAdd/UserAndTeamAccessAdd.test.jsx
@@ -56,6 +56,7 @@ describe('
', () => {
/>
);
});
+ await waitForElement(wrapper, 'PFWizard');
});
afterEach(() => {
wrapper.unmount();
@@ -68,12 +69,12 @@ describe('
', () => {
expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe(true);
expect(
wrapper
- .find('WizardNavItem[text="Select items from list"]')
+ .find('WizardNavItem[content="Select items from list"]')
.prop('isDisabled')
).toBe(true);
expect(
wrapper
- .find('WizardNavItem[text="Select roles to apply"]')
+ .find('WizardNavItem[content="Select roles to apply"]')
.prop('isDisabled')
).toBe(true);
await act(async () =>
@@ -89,17 +90,19 @@ describe('
', () => {
wrapper.find('Button[type="submit"]').prop('onClick')()
);
wrapper.update();
- expect(
- wrapper.find('WizardNavItem[text="Add resource type"]').prop('isDisabled')
- ).toBe(false);
expect(
wrapper
- .find('WizardNavItem[text="Select items from list"]')
+ .find('WizardNavItem[content="Add resource type"]')
.prop('isDisabled')
).toBe(false);
expect(
wrapper
- .find('WizardNavItem[text="Select roles to apply"]')
+ .find('WizardNavItem[content="Select items from list"]')
+ .prop('isDisabled')
+ ).toBe(false);
+ expect(
+ wrapper
+ .find('WizardNavItem[content="Select roles to apply"]')
.prop('isDisabled')
).toBe(true);
});
diff --git a/awx/ui_next/src/components/Wizard/Wizard.jsx b/awx/ui_next/src/components/Wizard/Wizard.jsx
index 99e884baad..9f737d7691 100644
--- a/awx/ui_next/src/components/Wizard/Wizard.jsx
+++ b/awx/ui_next/src/components/Wizard/Wizard.jsx
@@ -3,7 +3,7 @@ import styled from 'styled-components';
Wizard.displayName = 'PFWizard';
export default styled(Wizard)`
- .pf-c-data-toolbar__content {
+ .pf-c-toolbar__content {
padding: 0 !important;
}
`;
diff --git a/awx/ui_next/src/screens/Application/ApplicationAdd/ApplicationAdd.test.jsx b/awx/ui_next/src/screens/Application/ApplicationAdd/ApplicationAdd.test.jsx
index bcf39fe64c..2bc0eba47e 100644
--- a/awx/ui_next/src/screens/Application/ApplicationAdd/ApplicationAdd.test.jsx
+++ b/awx/ui_next/src/screens/Application/ApplicationAdd/ApplicationAdd.test.jsx
@@ -91,8 +91,8 @@ describe('
', () => {
wrapper.update();
expect(wrapper.find('input#name').prop('value')).toBe('new foo');
expect(wrapper.find('input#description').prop('value')).toBe('new bar');
- expect(wrapper.find('InnerChipGroup').length).toBe(1);
- expect(wrapper.find('InnerChipGroup').text()).toBe('organization');
+ expect(wrapper.find('Chip').length).toBe(1);
+ expect(wrapper.find('Chip').text()).toBe('organization');
expect(
wrapper
.find('AnsibleSelect[name="authorization_grant_type"]')
diff --git a/awx/ui_next/src/screens/Application/shared/ApplicationForm.test.jsx b/awx/ui_next/src/screens/Application/shared/ApplicationForm.test.jsx
index 406c3d5ace..f43fc878d2 100644
--- a/awx/ui_next/src/screens/Application/shared/ApplicationForm.test.jsx
+++ b/awx/ui_next/src/screens/Application/shared/ApplicationForm.test.jsx
@@ -107,8 +107,8 @@ describe('
{
wrapper.update();
expect(wrapper.find('input#name').prop('value')).toBe('new foo');
expect(wrapper.find('input#description').prop('value')).toBe('new bar');
- expect(wrapper.find('InnerChipGroup').length).toBe(1);
- expect(wrapper.find('InnerChipGroup').text()).toBe('organization');
+ expect(wrapper.find('Chip').length).toBe(1);
+ expect(wrapper.find('Chip').text()).toBe('organization');
expect(
wrapper
.find('AnsibleSelect[name="authorization_grant_type"]')
diff --git a/awx/ui_next/src/screens/AuthSetting/AuthSettings.jsx b/awx/ui_next/src/screens/AuthSetting/AuthSettings.jsx
index a0fc4b5f73..e9f79a452a 100644
--- a/awx/ui_next/src/screens/AuthSetting/AuthSettings.jsx
+++ b/awx/ui_next/src/screens/AuthSetting/AuthSettings.jsx
@@ -15,7 +15,9 @@ class AuthSettings extends Component {
return (
- {i18n._(t`Authentication Settings`)}
+
+ {i18n._(t`Authentication Settings`)}
+
diff --git a/awx/ui_next/src/screens/Credential/Credential.jsx b/awx/ui_next/src/screens/Credential/Credential.jsx
index fd318bc07c..baa1e34e0f 100644
--- a/awx/ui_next/src/screens/Credential/Credential.jsx
+++ b/awx/ui_next/src/screens/Credential/Credential.jsx
@@ -1,7 +1,8 @@
import React, { useEffect, useState } from 'react';
import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react';
-import { Card, PageSection, CardActions } from '@patternfly/react-core';
+import { CaretLeftIcon } from '@patternfly/react-icons';
+import { Card, PageSection } from '@patternfly/react-core';
import {
Switch,
useParams,
@@ -11,8 +12,6 @@ import {
Redirect,
Link,
} from 'react-router-dom';
-import { TabbedCardHeader } from '../../components/Card';
-import CardCloseButton from '../../components/CardCloseButton';
import { ResourceAccessList } from '../../components/ResourceAccessList';
import ContentError from '../../components/ContentError';
import RoutedTabs from '../../components/RoutedTabs';
@@ -46,6 +45,16 @@ function Credential({ i18n, setBreadcrumb }) {
}, [id, pathname, setBreadcrumb]);
const tabsArray = [
+ {
+ name: (
+ <>
+
+ {i18n._(t`Back to Credentials`)}
+ >
+ ),
+ link: `/credentials`,
+ id: 99,
+ },
{ name: i18n._(t`Details`), link: `/credentials/${id}/details`, id: 0 },
];
@@ -57,17 +66,10 @@ function Credential({ i18n, setBreadcrumb }) {
});
}
- let cardHeader = hasContentLoading ? null : (
-
-
-
-
-
-
- );
+ let showCardHeader = true;
if (pathname.endsWith('edit') || pathname.endsWith('add')) {
- cardHeader = null;
+ showCardHeader = false;
}
if (!hasContentLoading && contentError) {
@@ -90,7 +92,7 @@ function Credential({ i18n, setBreadcrumb }) {
return (
- {cardHeader}
+ {showCardHeader && }
', () => {
wrapper = mountWithContexts( {}} />);
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
- await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 1);
+ await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 2);
});
test('initially renders org-based credential succesfully', async () => {
@@ -44,7 +44,7 @@ describe(' ', () => {
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
// org-based credential detail needs access tab
- await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 2);
+ await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 3);
});
test('should show content error when user attempts to navigate to erroneous route', async () => {
diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialForm.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialForm.jsx
index de814d2630..bc486bb8b1 100644
--- a/awx/ui_next/src/screens/Credential/shared/CredentialForm.jsx
+++ b/awx/ui_next/src/screens/Credential/shared/CredentialForm.jsx
@@ -104,7 +104,9 @@ function CredentialFormFields({
fieldId="credential-credentialType"
helperTextInvalid={credTypeMeta.error}
isRequired
- isValid={!credTypeMeta.touched || !credTypeMeta.error}
+ validated={
+ !credTypeMeta.touched || !credTypeMeta.error ? 'default' : 'error'
+ }
label={i18n._(t`Credential Type`)}
>
', () => {
).toBe('');
});
test('should show error when error thrown parsing JSON', async () => {
+ await act(async () => {
+ await wrapper
+ .find('AnsibleSelect[id="credential_type"]')
+ .invoke('onChange')(null, 10);
+ });
+ wrapper.update();
expect(wrapper.find('#credential-gce-file-helper').text()).toBe(
'Select a JSON formatted service account key to autopopulate the following fields.'
);
@@ -201,7 +207,15 @@ describe(' ', () => {
});
});
wrapper.update();
- expect(wrapper.find('#credential-gce-file-helper').text()).toBe(
+ expect(
+ wrapper.find('FormGroup[fieldId="credential-gce-file"]').prop('isValid')
+ ).toBe(false);
+
+ expect(
+ wrapper
+ .find('FormGroup[fieldId="credential-gce-file"]')
+ .prop('helperTextInvalid')
+ ).toBe(
'There was an error parsing the file. Please check the file formatting and try again.'
);
});
diff --git a/awx/ui_next/src/screens/Credential/shared/TypeInputsSubForm.jsx b/awx/ui_next/src/screens/Credential/shared/TypeInputsSubForm.jsx
index d05e27d16e..c0a21b65b5 100644
--- a/awx/ui_next/src/screens/Credential/shared/TypeInputsSubForm.jsx
+++ b/awx/ui_next/src/screens/Credential/shared/TypeInputsSubForm.jsx
@@ -21,7 +21,9 @@ function TypeInputsSubForm({ credentialType, i18n }) {
);
return (
- {i18n._(t`Type Details`)}
+
+ {i18n._(t`Type Details`)}
+
{credentialType.namespace === 'gce' && }
{stringFields.map(fieldOptions =>
diff --git a/awx/ui_next/src/screens/Dashboard/Dashboard.jsx b/awx/ui_next/src/screens/Dashboard/Dashboard.jsx
index 0f2a7ceb79..b8e37fb1ad 100644
--- a/awx/ui_next/src/screens/Dashboard/Dashboard.jsx
+++ b/awx/ui_next/src/screens/Dashboard/Dashboard.jsx
@@ -15,7 +15,9 @@ class Dashboard extends Component {
return (
- {i18n._(t`Dashboard`)}
+
+ {i18n._(t`Dashboard`)}
+
diff --git a/awx/ui_next/src/screens/Host/Host.jsx b/awx/ui_next/src/screens/Host/Host.jsx
index d52737d195..536ac4f65b 100644
--- a/awx/ui_next/src/screens/Host/Host.jsx
+++ b/awx/ui_next/src/screens/Host/Host.jsx
@@ -9,10 +9,8 @@ import {
useRouteMatch,
useLocation,
} from 'react-router-dom';
-import { Card, CardActions, PageSection } from '@patternfly/react-core';
-
-import { TabbedCardHeader } from '../../components/Card';
-import CardCloseButton from '../../components/CardCloseButton';
+import { CaretLeftIcon } from '@patternfly/react-icons';
+import { Card, PageSection } from '@patternfly/react-core';
import RoutedTabs from '../../components/RoutedTabs';
import ContentError from '../../components/ContentError';
import ContentLoading from '../../components/ContentLoading';
@@ -47,6 +45,16 @@ function Host({ i18n, setBreadcrumb }) {
}, [match.params.id, location, setBreadcrumb]);
const tabsArray = [
+ {
+ name: (
+ <>
+
+ {i18n._(t`Back to Hosts`)}
+ >
+ ),
+ link: `/hosts`,
+ id: 99,
+ },
{
name: i18n._(t`Details`),
link: `${match.url}/details`,
@@ -96,17 +104,16 @@ function Host({ i18n, setBreadcrumb }) {
);
}
+ let showCardHeader = true;
+
+ if (location.pathname.endsWith('edit')) {
+ showCardHeader = false;
+ }
+
return (
- {location.pathname.endsWith('edit') ? null : (
-
-
-
-
-
-
- )}
+ {showCardHeader && }
{host && [
diff --git a/awx/ui_next/src/screens/InstanceGroup/InstanceGroups.jsx b/awx/ui_next/src/screens/InstanceGroup/InstanceGroups.jsx
index 022247193e..9a973bccc0 100644
--- a/awx/ui_next/src/screens/InstanceGroup/InstanceGroups.jsx
+++ b/awx/ui_next/src/screens/InstanceGroup/InstanceGroups.jsx
@@ -15,7 +15,9 @@ class InstanceGroups extends Component {
return (
- {i18n._(t`Instance Groups`)}
+
+ {i18n._(t`Instance Groups`)}
+
diff --git a/awx/ui_next/src/screens/Inventory/Inventory.jsx b/awx/ui_next/src/screens/Inventory/Inventory.jsx
index 8515029f4e..2e02d5f03b 100644
--- a/awx/ui_next/src/screens/Inventory/Inventory.jsx
+++ b/awx/ui_next/src/screens/Inventory/Inventory.jsx
@@ -9,10 +9,8 @@ import {
useLocation,
useRouteMatch,
} from 'react-router-dom';
-
-import { Card, CardActions, PageSection } from '@patternfly/react-core';
-import { TabbedCardHeader } from '../../components/Card';
-import CardCloseButton from '../../components/CardCloseButton';
+import { CaretLeftIcon } from '@patternfly/react-icons';
+import { Card, PageSection } from '@patternfly/react-core';
import ContentError from '../../components/ContentError';
import ContentLoading from '../../components/ContentLoading';
import JobList from '../../components/JobList';
@@ -51,6 +49,16 @@ function Inventory({ i18n, setBreadcrumb }) {
}, [match.params.id, location.pathname, setBreadcrumb]);
const tabsArray = [
+ {
+ name: (
+ <>
+
+ {i18n._(t`Back to Inventories`)}
+ >
+ ),
+ link: `/inventories`,
+ id: 99,
+ },
{ name: i18n._(t`Details`), link: `${match.url}/details`, id: 0 },
{ name: i18n._(t`Access`), link: `${match.url}/access`, id: 1 },
{ name: i18n._(t`Groups`), link: `${match.url}/groups`, id: 2 },
@@ -90,19 +98,20 @@ function Inventory({ i18n, setBreadcrumb }) {
);
}
+ let showCardHeader = true;
+
+ if (
+ ['edit', 'add', 'groups/', 'hosts/', 'sources/'].some(name =>
+ location.pathname.includes(name)
+ )
+ ) {
+ showCardHeader = false;
+ }
+
return (
- {['edit', 'add', 'groups/', 'hosts/', 'sources/'].some(name =>
- location.pathname.includes(name)
- ) ? null : (
-
-
-
-
-
-
- )}
+ {showCardHeader && }
', () => {
wrapper = mountWithContexts( {}} />);
});
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
- await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 6);
+ await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 7);
});
test('should show content error when user attempts to navigate to erroneous route', async () => {
diff --git a/awx/ui_next/src/screens/Inventory/InventoryGroup/InventoryGroup.jsx b/awx/ui_next/src/screens/Inventory/InventoryGroup/InventoryGroup.jsx
index 28ac2daded..9818bd43de 100644
--- a/awx/ui_next/src/screens/Inventory/InventoryGroup/InventoryGroup.jsx
+++ b/awx/ui_next/src/screens/Inventory/InventoryGroup/InventoryGroup.jsx
@@ -10,13 +10,10 @@ import {
useLocation,
useParams,
} from 'react-router-dom';
-import { CardActions } from '@patternfly/react-core';
import { CaretLeftIcon } from '@patternfly/react-icons';
-import CardCloseButton from '../../../components/CardCloseButton';
import RoutedTabs from '../../../components/RoutedTabs';
import ContentError from '../../../components/ContentError';
import ContentLoading from '../../../components/ContentLoading';
-import { TabbedCardHeader } from '../../../components/Card';
import InventoryGroupEdit from '../InventoryGroupEdit/InventoryGroupEdit';
import InventoryGroupDetail from '../InventoryGroupDetail/InventoryGroupDetail';
import InventoryGroupHosts from '../InventoryGroupHosts';
@@ -99,18 +96,14 @@ function InventoryGroup({ i18n, setBreadcrumb, inventory }) {
);
}
+ let showCardHeader = true;
+ if (['add', 'edit'].some(name => location.pathname.includes(name))) {
+ showCardHeader = false;
+ }
+
return (
<>
- {['add', 'edit'].some(name => location.pathname.includes(name)) ? null : (
-
-
-
-
-
-
- )}
+ {showCardHeader && }
', () => {
});
wrapper.update();
await act(async () => {
- wrapper
- .find('DataToolbar Button[aria-label="Delete"]')
- .invoke('onClick')();
+ wrapper.find('Toolbar Button[aria-label="Delete"]').invoke('onClick')();
});
await waitForElement(
wrapper,
@@ -197,14 +195,12 @@ describe(' ', () => {
});
wrapper.update();
await act(async () => {
- wrapper
- .find('DataToolbar Button[aria-label="Delete"]')
- .invoke('onClick')();
+ wrapper.find('Toolbar Button[aria-label="Delete"]').invoke('onClick')();
});
await waitForElement(
wrapper,
- 'InventoryGroupsDeleteModal',
- el => el.props().isModalOpen === true
+ 'AlertModal[title="Delete Group?"]',
+ el => el.props().isOpen === true
);
await act(async () => {
wrapper.find('Radio[id="radio-delete"]').invoke('onChange')();
@@ -215,7 +211,11 @@ describe(' ', () => {
.find('ModalBoxFooter Button[aria-label="Delete"]')
.invoke('onClick')();
});
- await waitForElement(wrapper, { title: 'Error!', variant: 'error' });
+ await waitForElement(
+ wrapper,
+ 'AlertModal[title="Error!"] Modal',
+ el => el.props().isOpen === true && el.props().title === 'Error!'
+ );
await act(async () => {
wrapper.find('ModalBoxCloseButton').invoke('onClose')();
});
diff --git a/awx/ui_next/src/screens/Inventory/InventoryHost/InventoryHost.jsx b/awx/ui_next/src/screens/Inventory/InventoryHost/InventoryHost.jsx
index ef674bc146..33e7884a73 100644
--- a/awx/ui_next/src/screens/Inventory/InventoryHost/InventoryHost.jsx
+++ b/awx/ui_next/src/screens/Inventory/InventoryHost/InventoryHost.jsx
@@ -9,13 +9,11 @@ import {
useRouteMatch,
useLocation,
} from 'react-router-dom';
-import { Card, CardActions } from '@patternfly/react-core';
+import { Card } from '@patternfly/react-core';
import { CaretLeftIcon } from '@patternfly/react-icons';
import useRequest from '../../../util/useRequest';
import { InventoriesAPI } from '../../../api';
-import { TabbedCardHeader } from '../../../components/Card';
-import CardCloseButton from '../../../components/CardCloseButton';
import ContentError from '../../../components/ContentError';
import ContentLoading from '../../../components/ContentLoading';
import RoutedTabs from '../../../components/RoutedTabs';
@@ -110,16 +108,14 @@ function InventoryHost({ i18n, setBreadcrumb, inventory }) {
);
}
+ let showCardHeader = true;
+ if (['edit'].some(name => location.pathname.includes(name))) {
+ showCardHeader = false;
+ }
+
return (
<>
- {['edit'].some(name => location.pathname.includes(name)) ? null : (
-
-
-
-
-
-
- )}
+ {showCardHeader && }
{isLoading && }
diff --git a/awx/ui_next/src/screens/Inventory/InventorySource/InventorySource.jsx b/awx/ui_next/src/screens/Inventory/InventorySource/InventorySource.jsx
index ecd065a57b..771401b7e4 100644
--- a/awx/ui_next/src/screens/Inventory/InventorySource/InventorySource.jsx
+++ b/awx/ui_next/src/screens/Inventory/InventorySource/InventorySource.jsx
@@ -10,7 +10,6 @@ import {
useLocation,
} from 'react-router-dom';
import { CaretLeftIcon } from '@patternfly/react-icons';
-import { CardActions } from '@patternfly/react-core';
import useRequest from '../../../util/useRequest';
import {
@@ -18,9 +17,7 @@ import {
InventorySourcesAPI,
OrganizationsAPI,
} from '../../../api';
-import { TabbedCardHeader } from '../../../components/Card';
import { Schedules } from '../../../components/Schedule';
-import CardCloseButton from '../../../components/CardCloseButton';
import ContentError from '../../../components/ContentError';
import ContentLoading from '../../../components/ContentLoading';
import RoutedTabs from '../../../components/RoutedTabs';
@@ -112,18 +109,15 @@ function InventorySource({ i18n, inventory, setBreadcrumb, me }) {
return ;
}
+ let showCardHeader = true;
+
+ if (['edit', 'schedules/'].some(name => location.pathname.includes(name))) {
+ showCardHeader = false;
+ }
+
return (
<>
- {['edit', 'schedules/'].some(name =>
- location.pathname.includes(name)
- ) ? null : (
-
-
-
-
-
-
- )}
+ {showCardHeader && }
{isLoading && }
diff --git a/awx/ui_next/src/screens/Inventory/SmartInventory.jsx b/awx/ui_next/src/screens/Inventory/SmartInventory.jsx
index 29e503faa9..f35876c768 100644
--- a/awx/ui_next/src/screens/Inventory/SmartInventory.jsx
+++ b/awx/ui_next/src/screens/Inventory/SmartInventory.jsx
@@ -1,10 +1,9 @@
import React, { Component } from 'react';
import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react';
-import { Card, CardActions, PageSection } from '@patternfly/react-core';
+import { CaretLeftIcon } from '@patternfly/react-icons';
+import { Card, PageSection } from '@patternfly/react-core';
import { Switch, Route, Redirect, withRouter, Link } from 'react-router-dom';
-import { TabbedCardHeader } from '../../components/Card';
-import CardCloseButton from '../../components/CardCloseButton';
import ContentError from '../../components/ContentError';
import JobList from '../../components/JobList';
import RoutedTabs from '../../components/RoutedTabs';
@@ -64,6 +63,16 @@ class SmartInventory extends Component {
const { contentError, hasContentLoading, inventory } = this.state;
const tabsArray = [
+ {
+ name: (
+ <>
+
+ {i18n._(t`Back to Inventories`)}
+ >
+ ),
+ link: `/inventories`,
+ id: 99,
+ },
{ name: i18n._(t`Details`), link: `${match.url}/details`, id: 0 },
{ name: i18n._(t`Access`), link: `${match.url}/access`, id: 1 },
{ name: i18n._(t`Hosts`), link: `${match.url}/hosts`, id: 2 },
@@ -74,17 +83,10 @@ class SmartInventory extends Component {
},
];
- let cardHeader = hasContentLoading ? null : (
-
-
-
-
-
-
- );
+ let showCardHeader = true;
if (location.pathname.endsWith('edit')) {
- cardHeader = null;
+ showCardHeader = false;
}
if (!hasContentLoading && contentError) {
@@ -108,7 +110,7 @@ class SmartInventory extends Component {
return (
- {cardHeader}
+ {showCardHeader && }
', () => {
'SmartInventory',
el => el.state('hasContentLoading') === false
);
- await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 4);
+ await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 5);
done();
});
test('should show content error when user attempts to navigate to erroneous route', async () => {
diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceForm.jsx
index ffcab9f148..73dec8877f 100644
--- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceForm.jsx
+++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceForm.jsx
@@ -117,7 +117,9 @@ const InventorySourceFormFields = ({ sourceOptions, i18n }) => {
fieldId="source"
helperTextInvalid={sourceMeta.error}
isRequired
- isValid={!sourceMeta.touched || !sourceMeta.error}
+ validated={
+ !sourceMeta.touched || !sourceMeta.error ? 'default' : 'error'
+ }
label={i18n._(t`Source`)}
>
{
)}
{sourceField.value !== '' && (
- {i18n._(t`Source details`)}
+
+ {i18n._(t`Source details`)}
+
{
{
diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.jsx
index e9fe7f6690..3689e305a8 100644
--- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.jsx
+++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SCMSubForm.jsx
@@ -83,9 +83,11 @@ const SCMSubForm = ({ i18n }) => {
{
return (
- {i18n._(t`Inventory Scripts`)}
+
+ {i18n._(t`Inventory Scripts`)}
+
diff --git a/awx/ui_next/src/screens/Job/Job.jsx b/awx/ui_next/src/screens/Job/Job.jsx
index 624f7443ff..924ebfebcc 100644
--- a/awx/ui_next/src/screens/Job/Job.jsx
+++ b/awx/ui_next/src/screens/Job/Job.jsx
@@ -2,11 +2,10 @@ import React, { Component } from 'react';
import { Route, withRouter, Switch, Redirect, Link } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
-import { Card, CardActions, PageSection } from '@patternfly/react-core';
+import { CaretLeftIcon } from '@patternfly/react-icons';
+import { Card, PageSection } from '@patternfly/react-core';
import { JobsAPI } from '../../api';
-import { TabbedCardHeader } from '../../components/Card';
import ContentError from '../../components/ContentError';
-import CardCloseButton from '../../components/CardCloseButton';
import RoutedTabs from '../../components/RoutedTabs';
import JobDetail from './JobDetail';
@@ -67,21 +66,24 @@ class Job extends Component {
}
const tabsArray = [
+ {
+ name: (
+ <>
+
+ {i18n._(t`Back to Jobs`)}
+ >
+ ),
+ link: `/jobs`,
+ id: 99,
+ },
{ name: i18n._(t`Details`), link: `${match.url}/details`, id: 0 },
{ name: i18n._(t`Output`), link: `${match.url}/output`, id: 1 },
];
- let cardHeader = (
-
-
-
-
-
-
- );
+ let showCardHeader = true;
if (!isInitialized) {
- cardHeader = null;
+ showCardHeader = false;
}
if (!hasContentLoading && contentError) {
@@ -117,7 +119,7 @@ class Job extends Component {
return (
- {cardHeader}
+ {showCardHeader && }
{
let status = null;
if (event.event === 'runner_on_unreachable') {
@@ -133,11 +96,11 @@ function HostEventModal({ onClose, hostEvent = {}, isOpen = false, i18n }) {
return (
{i18n._(t`Details`)}}
>
-
+
{i18n._(t`JSON`)}}
aria-label={i18n._(t`JSON tab`)}
>
{activeTabKey === 1 && jsonObj ? (
@@ -193,7 +159,7 @@ function HostEventModal({ onClose, hostEvent = {}, isOpen = false, i18n }) {
{i18n._(t`Standard Out`)}}
aria-label={i18n._(t`Standard out tab`)}
>
{activeTabKey === 2 && stdOut ? (
@@ -211,7 +177,7 @@ function HostEventModal({ onClose, hostEvent = {}, isOpen = false, i18n }) {
{i18n._(t`Standard Error`)}}
aria-label={i18n._(t`Standard error tab`)}
>
{activeTabKey === 3 && stdErr ? (
diff --git a/awx/ui_next/src/screens/Job/JobOutput/HostEventModal.test.jsx b/awx/ui_next/src/screens/Job/JobOutput/HostEventModal.test.jsx
index 0a18cc3d93..01b6ed61bd 100644
--- a/awx/ui_next/src/screens/Job/JobOutput/HostEventModal.test.jsx
+++ b/awx/ui_next/src/screens/Job/JobOutput/HostEventModal.test.jsx
@@ -88,16 +88,7 @@ describe('HostEventModal', () => {
);
/* eslint-disable react/button-has-type */
- expect(
- wrapper
- .find('Tabs')
- .containsAllMatchingElements([
- Details ,
- JSON ,
- Standard Out ,
- Standard Error ,
- ])
- ).toEqual(true);
+ expect(wrapper.find('Tabs TabButton').length).toEqual(4);
});
test('should show details tab content on mount', () => {
diff --git a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
index 4e72d0014c..9c6b324891 100644
--- a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
+++ b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
@@ -475,6 +475,7 @@ class JobOutput extends Component {
variant="danger"
onClose={() => this.setState({ deletionError: null })}
title={i18n._(t`Job Delete Error`)}
+ label={i18n._(t`Job Delete Error`)}
>
diff --git a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.test.jsx b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.test.jsx
index 5afa5d2573..5531a0ade6 100644
--- a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.test.jsx
+++ b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.test.jsx
@@ -338,7 +338,7 @@ describe(' ', () => {
wrapper.find('Modal button[aria-label="Delete"]').simulate('click');
await waitForElement(wrapper, 'Modal ErrorDetail');
const errorModalCloseBtn = wrapper.find(
- 'ModalBox div[aria-label="Job Delete Error"] button[aria-label="Close"]'
+ 'ModalBox[aria-label="Job Delete Error"] ModalBoxCloseButton'
);
errorModalCloseBtn.simulate('click');
await waitForElement(wrapper, 'Modal ErrorDetail', el => el.length === 0);
diff --git a/awx/ui_next/src/screens/JobsSetting/JobsSettings.jsx b/awx/ui_next/src/screens/JobsSetting/JobsSettings.jsx
index ded44676f6..f5fb77ef50 100644
--- a/awx/ui_next/src/screens/JobsSetting/JobsSettings.jsx
+++ b/awx/ui_next/src/screens/JobsSetting/JobsSettings.jsx
@@ -15,7 +15,9 @@ class JobsSettings extends Component {
return (
- {i18n._(t`Jobs Settings`)}
+
+ {i18n._(t`Jobs Settings`)}
+
diff --git a/awx/ui_next/src/screens/License/License.jsx b/awx/ui_next/src/screens/License/License.jsx
index cebf2a4734..1ec59d2930 100644
--- a/awx/ui_next/src/screens/License/License.jsx
+++ b/awx/ui_next/src/screens/License/License.jsx
@@ -15,7 +15,9 @@ class License extends Component {
return (
- {i18n._(t`License`)}
+
+ {i18n._(t`License`)}
+
diff --git a/awx/ui_next/src/screens/ManagementJob/ManagementJobs.jsx b/awx/ui_next/src/screens/ManagementJob/ManagementJobs.jsx
index f86e9b3040..658cfcb403 100644
--- a/awx/ui_next/src/screens/ManagementJob/ManagementJobs.jsx
+++ b/awx/ui_next/src/screens/ManagementJob/ManagementJobs.jsx
@@ -15,7 +15,9 @@ class ManagementJobs extends Component {
return (
- {i18n._(t`Management Jobs`)}
+
+ {i18n._(t`Management Jobs`)}
+
diff --git a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplates.jsx b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplates.jsx
index abcf1ab511..857201bc6b 100644
--- a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplates.jsx
+++ b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplates.jsx
@@ -15,7 +15,9 @@ class NotificationTemplates extends Component {
return (
- {i18n._(t`Notification Templates`)}
+
+ {i18n._(t`Notification Templates`)}
+
diff --git a/awx/ui_next/src/screens/Organization/Organization.jsx b/awx/ui_next/src/screens/Organization/Organization.jsx
index 6dfb120c29..248f5fc430 100644
--- a/awx/ui_next/src/screens/Organization/Organization.jsx
+++ b/awx/ui_next/src/screens/Organization/Organization.jsx
@@ -2,9 +2,8 @@ import React, { Component } from 'react';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Switch, Route, withRouter, Redirect, Link } from 'react-router-dom';
-import { Card, CardActions, PageSection } from '@patternfly/react-core';
-import CardCloseButton from '../../components/CardCloseButton';
-import { TabbedCardHeader } from '../../components/Card';
+import { CaretLeftIcon } from '@patternfly/react-icons';
+import { Card, PageSection } from '@patternfly/react-core';
import RoutedTabs from '../../components/RoutedTabs';
import ContentError from '../../components/ContentError';
import NotificationList from '../../components/NotificationList/NotificationList';
@@ -116,6 +115,16 @@ class Organization extends Component {
(me.is_system_auditor || isAuditorOfThisOrg || isAdminOfThisOrg);
const tabsArray = [
+ {
+ name: (
+ <>
+
+ {i18n._(t`Back to Organizations`)}
+ >
+ ),
+ link: `/organizations`,
+ id: 99,
+ },
{ name: i18n._(t`Details`), link: `${match.url}/details`, id: 0 },
{ name: i18n._(t`Access`), link: `${match.url}/access`, id: 1 },
{ name: i18n._(t`Teams`), link: `${match.url}/teams`, id: 2 },
@@ -129,21 +138,10 @@ class Organization extends Component {
});
}
- let cardHeader = (
-
-
-
-
-
-
- );
+ let showCardHeader = true;
- if (!isInitialized) {
- cardHeader = null;
- }
-
- if (location.pathname.endsWith('edit')) {
- cardHeader = null;
+ if (!isInitialized || location.pathname.endsWith('edit')) {
+ showCardHeader = false;
}
if (!hasContentLoading && contentError) {
@@ -168,7 +166,7 @@ class Organization extends Component {
return (
- {cardHeader}
+ {showCardHeader && }
', () => {
const tabs = await waitForElement(
wrapper,
'.pf-c-tabs__item',
- el => el.length === 4
+ el => el.length === 5
);
expect(tabs.last().text()).toEqual('Notifications');
done();
@@ -74,7 +74,7 @@ describe(' ', () => {
const tabs = await waitForElement(
wrapper,
'.pf-c-tabs__item',
- el => el.length === 3
+ el => el.length === 4
);
tabs.forEach(tab => expect(tab.text()).not.toEqual('Notifications'));
done();
diff --git a/awx/ui_next/src/screens/Portal/Portal.jsx b/awx/ui_next/src/screens/Portal/Portal.jsx
index d5f6cbecec..6651fc25d2 100644
--- a/awx/ui_next/src/screens/Portal/Portal.jsx
+++ b/awx/ui_next/src/screens/Portal/Portal.jsx
@@ -15,7 +15,9 @@ class Portal extends Component {
return (
- {i18n._(t`My View`)}
+
+ {i18n._(t`My View`)}
+
diff --git a/awx/ui_next/src/screens/Project/Project.jsx b/awx/ui_next/src/screens/Project/Project.jsx
index f20b66e3ca..3dae7a5a6c 100644
--- a/awx/ui_next/src/screens/Project/Project.jsx
+++ b/awx/ui_next/src/screens/Project/Project.jsx
@@ -2,9 +2,8 @@ import React, { Component } from 'react';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Switch, Route, withRouter, Redirect, Link } from 'react-router-dom';
-import { Card, CardActions, PageSection } from '@patternfly/react-core';
-import { TabbedCardHeader } from '../../components/Card';
-import CardCloseButton from '../../components/CardCloseButton';
+import { CaretLeftIcon } from '@patternfly/react-icons';
+import { Card, PageSection } from '@patternfly/react-core';
import RoutedTabs from '../../components/RoutedTabs';
import ContentError from '../../components/ContentError';
import NotificationList from '../../components/NotificationList';
@@ -121,6 +120,16 @@ class Project extends Component {
const canToggleNotifications = isNotifAdmin;
const tabsArray = [
+ {
+ name: (
+ <>
+
+ {i18n._(t`Back to Projects`)}
+ >
+ ),
+ link: `/projects`,
+ id: 99,
+ },
{ name: i18n._(t`Details`), link: `${match.url}/details` },
{ name: i18n._(t`Access`), link: `${match.url}/access` },
];
@@ -148,24 +157,14 @@ class Project extends Component {
tab.id = n;
});
- let cardHeader = (
-
-
-
-
-
-
- );
-
- if (!isInitialized) {
- cardHeader = null;
- }
+ let showCardHeader = true;
if (
+ !isInitialized ||
location.pathname.endsWith('edit') ||
location.pathname.includes('schedules/')
) {
- cardHeader = null;
+ showCardHeader = false;
}
if (!hasContentLoading && contentError) {
@@ -188,7 +187,7 @@ class Project extends Component {
return (
- {cardHeader}
+ {showCardHeader && }
{project && (
diff --git a/awx/ui_next/src/screens/Project/Project.test.jsx b/awx/ui_next/src/screens/Project/Project.test.jsx
index f2ecf09cc7..7d8080e8a9 100644
--- a/awx/ui_next/src/screens/Project/Project.test.jsx
+++ b/awx/ui_next/src/screens/Project/Project.test.jsx
@@ -44,9 +44,9 @@ describe(' ', () => {
const tabs = await waitForElement(
wrapper,
'.pf-c-tabs__item',
- el => el.length === 5
+ el => el.length === 6
);
- expect(tabs.at(2).text()).toEqual('Notifications');
+ expect(tabs.at(3).text()).toEqual('Notifications');
done();
});
@@ -65,7 +65,7 @@ describe(' ', () => {
const tabs = await waitForElement(
wrapper,
'.pf-c-tabs__item',
- el => el.length === 4
+ el => el.length === 5
);
tabs.forEach(tab => expect(tab.text()).not.toEqual('Notifications'));
done();
@@ -86,9 +86,9 @@ describe(' ', () => {
const tabs = await waitForElement(
wrapper,
'.pf-c-tabs__item',
- el => el.length === 4
+ el => el.length === 5
);
- expect(tabs.at(3).text()).toEqual('Schedules');
+ expect(tabs.at(4).text()).toEqual('Schedules');
done();
});
@@ -108,7 +108,7 @@ describe(' ', () => {
const tabs = await waitForElement(
wrapper,
'.pf-c-tabs__item',
- el => el.length === 3
+ el => el.length === 4
);
tabs.forEach(tab => expect(tab.text()).not.toEqual('Schedules'));
done();
diff --git a/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx b/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx
index eee485d646..692c66d3d2 100644
--- a/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx
+++ b/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx
@@ -172,7 +172,9 @@ function ProjectFormFields({
fieldId="project-scm-type"
helperTextInvalid={scmTypeMeta.error}
isRequired
- isValid={!scmTypeMeta.touched || !scmTypeMeta.error}
+ validated={
+ !scmTypeMeta.touched || !scmTypeMeta.error ? 'default' : 'error'
+ }
label={i18n._(t`Source Control Credential Type`)}
>
{formik.values.scm_type !== '' && (
- {i18n._(t`Type Details`)}
+
+ {i18n._(t`Type Details`)}
+
{
{
diff --git a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/ManualSubForm.jsx b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/ManualSubForm.jsx
index 96646238d9..b6ece76103 100644
--- a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/ManualSubForm.jsx
+++ b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/ManualSubForm.jsx
@@ -81,7 +81,7 @@ const ManualSubForm = ({
fieldId="project-local-path"
helperTextInvalid={pathMeta.error}
isRequired
- isValid={!pathMeta.touched || !pathMeta.error}
+ validated={!pathMeta.touched || !pathMeta.error ? 'default' : 'error'}
label={i18n._(t`Playbook Directory`)}
>
- {i18n._(t`Option Details`)}
+
+ {i18n._(t`Option Details`)}
+
- {i18n._(t`System Settings`)}
+
+ {i18n._(t`System Settings`)}
+
diff --git a/awx/ui_next/src/screens/Team/Team.jsx b/awx/ui_next/src/screens/Team/Team.jsx
index b7e878b415..c3366af422 100644
--- a/awx/ui_next/src/screens/Team/Team.jsx
+++ b/awx/ui_next/src/screens/Team/Team.jsx
@@ -9,9 +9,8 @@ import {
useLocation,
useParams,
} from 'react-router-dom';
-import { Card, CardActions, PageSection } from '@patternfly/react-core';
-import CardCloseButton from '../../components/CardCloseButton';
-import { TabbedCardHeader } from '../../components/Card';
+import { CaretLeftIcon } from '@patternfly/react-icons';
+import { Card, PageSection } from '@patternfly/react-core';
import RoutedTabs from '../../components/RoutedTabs';
import ContentError from '../../components/ContentError';
import TeamDetail from './TeamDetail';
@@ -41,22 +40,25 @@ function Team({ i18n, setBreadcrumb }) {
}, [id, setBreadcrumb, location]);
const tabsArray = [
+ {
+ name: (
+ <>
+
+ {i18n._(t`Back to Teams`)}
+ >
+ ),
+ link: `/teams`,
+ id: 99,
+ },
{ name: i18n._(t`Details`), link: `/teams/${id}/details`, id: 0 },
{ name: i18n._(t`Users`), link: `/teams/${id}/users`, id: 1 },
{ name: i18n._(t`Access`), link: `/teams/${id}/access`, id: 2 },
];
- let cardHeader = (
-
-
-
-
-
-
- );
+ let showCardHeader = true;
if (location.pathname.endsWith('edit')) {
- cardHeader = null;
+ showCardHeader = false;
}
if (!hasContentLoading && contentError) {
@@ -79,7 +81,7 @@ function Team({ i18n, setBreadcrumb }) {
return (
- {cardHeader}
+ {showCardHeader && }
{team && (
diff --git a/awx/ui_next/src/screens/Team/TeamAccess/TeamAccessListItem.jsx b/awx/ui_next/src/screens/Team/TeamAccess/TeamAccessListItem.jsx
index 12495c8003..e32199d19e 100644
--- a/awx/ui_next/src/screens/Team/TeamAccess/TeamAccessListItem.jsx
+++ b/awx/ui_next/src/screens/Team/TeamAccess/TeamAccessListItem.jsx
@@ -40,12 +40,12 @@ function TeamAccessListItem({ role, i18n, detailUrl, onSelect }) {
label={i18n._(t`Role`)}
value={
onSelect(role)}
+ isReadOnly={
+ !role.summary_fields.user_capabilities.unattach
+ }
>
{role.name}
diff --git a/awx/ui_next/src/screens/Template/Survey/SurveyList.test.jsx b/awx/ui_next/src/screens/Template/Survey/SurveyList.test.jsx
index 755956a5f4..bdc70c4fff 100644
--- a/awx/ui_next/src/screens/Template/Survey/SurveyList.test.jsx
+++ b/awx/ui_next/src/screens/Template/Survey/SurveyList.test.jsx
@@ -142,7 +142,7 @@ describe(' ', () => {
expect(
wrapper
- .find('DataToolbar')
+ .find('Toolbar')
.find('Checkbox')
.prop('isDisabled')
).toBe(true);
diff --git a/awx/ui_next/src/screens/Template/Survey/SurveyPreviewModal.jsx b/awx/ui_next/src/screens/Template/Survey/SurveyPreviewModal.jsx
index e9d8f0a1d2..f26f1f8ed1 100644
--- a/awx/ui_next/src/screens/Template/Survey/SurveyPreviewModal.jsx
+++ b/awx/ui_next/src/screens/Template/Survey/SurveyPreviewModal.jsx
@@ -30,9 +30,10 @@ function SurveyPreviewModal({
return (
onToggleModalOpen(false)}
- isSmall
+ variant="small"
>
{() => (
@@ -97,7 +98,7 @@ function SurveyPreviewModal({
isDisabled
isReadOnly
variant={SelectVariant.typeaheadMulti}
- isExpanded={false}
+ isOpen={false}
selections={q.default.length > 0 && q.default.split('\n')}
onToggle={() => {}}
aria-label={i18n._(t`Multi-Select`)}
diff --git a/awx/ui_next/src/screens/Template/Survey/SurveyPreviewModal.test.jsx b/awx/ui_next/src/screens/Template/Survey/SurveyPreviewModal.test.jsx
index ec56a7c7b7..74aac44a3f 100644
--- a/awx/ui_next/src/screens/Template/Survey/SurveyPreviewModal.test.jsx
+++ b/awx/ui_next/src/screens/Template/Survey/SurveyPreviewModal.test.jsx
@@ -106,19 +106,20 @@ describe(' ', () => {
.find('Select[aria-label="Multi-Select"]')
.find('Chip');
- expect(question1.text()).toBe('Text Question');
+ expect(question1.text()).toBe('Text Question ');
expect(question1Value.prop('value')).toBe('Text Question Value');
expect(question1Value.prop('isDisabled')).toBe(true);
expect(question2.text()).toBe('Select Question');
- expect(question2Value.find('span').text()).toBe('Select Question Value');
+ expect(question2Value.find('.pf-c-select__toggle-text').text()).toBe(
+ 'Select Question Value'
+ );
expect(question2Value.prop('isDisabled')).toBe(true);
expect(question3.text()).toBe('Text Area Question');
expect(question3Value.prop('value')).toBe('Text Area Question Value');
expect(question3Value.prop('disabled')).toBe(true);
-
- expect(question4.text()).toBe('Password Question');
+ expect(question4.text()).toBe('Password Question ');
expect(question4Value.prop('placeholder')).toBe('ENCRYPTED');
expect(question4Value.prop('isDisabled')).toBe(true);
diff --git a/awx/ui_next/src/screens/Template/Survey/SurveyToolbar.jsx b/awx/ui_next/src/screens/Template/Survey/SurveyToolbar.jsx
index af6144025a..16eac2be70 100644
--- a/awx/ui_next/src/screens/Template/Survey/SurveyToolbar.jsx
+++ b/awx/ui_next/src/screens/Template/Survey/SurveyToolbar.jsx
@@ -5,15 +5,17 @@ import { withI18n } from '@lingui/react';
import styled from 'styled-components';
import {
- DataToolbar as _DataToolbar,
- DataToolbarContent,
- DataToolbarGroup,
- DataToolbarItem,
-} from '@patternfly/react-core/dist/umd/experimental';
-import { Switch, Checkbox, Button } from '@patternfly/react-core';
+ Switch,
+ Checkbox,
+ Button,
+ Toolbar as _Toolbar,
+ ToolbarContent,
+ ToolbarGroup,
+ ToolbarItem,
+} from '@patternfly/react-core';
import { ToolbarAddButton } from '../../../components/PaginatedDataList';
-const DataToolbar = styled(_DataToolbar)`
+const Toolbar = styled(_Toolbar)`
margin-left: 52px;
`;
@@ -30,9 +32,9 @@ function SurveyToolbar({
isDeleteDisabled = !canEdit || isDeleteDisabled;
const match = useRouteMatch();
return (
-
-
-
+
+
+
-
-
+
+
onToggleSurvey(!surveyEnabled)}
/>
-
-
-
+
+
+
-
-
+
+
{i18n._(t`Delete`)}
-
-
-
-
+
+
+
+
);
}
diff --git a/awx/ui_next/src/screens/Template/Survey/SurveyToolbar.test.jsx b/awx/ui_next/src/screens/Template/Survey/SurveyToolbar.test.jsx
index 8fb4857525..2311f1d443 100644
--- a/awx/ui_next/src/screens/Template/Survey/SurveyToolbar.test.jsx
+++ b/awx/ui_next/src/screens/Template/Survey/SurveyToolbar.test.jsx
@@ -102,7 +102,7 @@ describe(' ', () => {
});
expect(
wrapper
- .find('DataToolbar')
+ .find('Toolbar')
.find('Checkbox')
.prop('isDisabled')
).toBe(true);
diff --git a/awx/ui_next/src/screens/Template/Template.jsx b/awx/ui_next/src/screens/Template/Template.jsx
index 7110f025fc..5bd84e8383 100644
--- a/awx/ui_next/src/screens/Template/Template.jsx
+++ b/awx/ui_next/src/screens/Template/Template.jsx
@@ -1,7 +1,8 @@
import React, { useEffect, useCallback } from 'react';
import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react';
-import { Card, CardActions, PageSection } from '@patternfly/react-core';
+import { CaretLeftIcon } from '@patternfly/react-icons';
+import { Card, PageSection } from '@patternfly/react-core';
import {
Switch,
Route,
@@ -11,14 +12,11 @@ import {
useParams,
useRouteMatch,
} from 'react-router-dom';
+import RoutedTabs from '../../components/RoutedTabs';
import useRequest from '../../util/useRequest';
-
-import { TabbedCardHeader } from '../../components/Card';
-import CardCloseButton from '../../components/CardCloseButton';
import ContentError from '../../components/ContentError';
import JobList from '../../components/JobList';
import NotificationList from '../../components/NotificationList';
-import RoutedTabs from '../../components/RoutedTabs';
import { Schedules } from '../../components/Schedule';
import { ResourceAccessList } from '../../components/ResourceAccessList';
import JobTemplateDetail from './JobTemplateDetail';
@@ -82,6 +80,16 @@ function Template({ i18n, me, setBreadcrumb }) {
template?.summary_fields?.user_capabilities.delete;
const tabsArray = [
+ {
+ name: (
+ <>
+
+ {i18n._(t`Back to Templates`)}
+ >
+ ),
+ link: `/templates`,
+ id: 99,
+ },
{ name: i18n._(t`Details`), link: `${match.url}/details` },
{ name: i18n._(t`Access`), link: `${match.url}/access` },
];
@@ -115,19 +123,13 @@ function Template({ i18n, me, setBreadcrumb }) {
tab.id = n;
});
- let cardHeader = (
-
-
-
-
-
-
- );
+ let showCardHeader = true;
+
if (
location.pathname.endsWith('edit') ||
location.pathname.includes('schedules/')
) {
- cardHeader = null;
+ showCardHeader = false;
}
const contentError = rolesAndTemplateError;
@@ -151,7 +153,7 @@ function Template({ i18n, me, setBreadcrumb }) {
return (
- {cardHeader}
+ {showCardHeader && }
', () => {
const tabs = await waitForElement(
wrapper,
'.pf-c-tabs__item',
- el => el.length === 6
+ el => el.length === 7
);
- expect(tabs.at(2).text()).toEqual('Notifications');
+ expect(tabs.at(3).text()).toEqual('Notifications');
done();
});
test('notifications tab hidden with reduced permissions', async done => {
@@ -83,7 +83,7 @@ describe(' ', () => {
const tabs = await waitForElement(
wrapper,
'.pf-c-tabs__item',
- el => el.length === 5
+ el => el.length === 6
);
tabs.forEach(tab => expect(tab.text()).not.toEqual('Notifications'));
done();
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplate.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplate.jsx
index eb40b93f4f..9706f89ae2 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplate.jsx
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplate.jsx
@@ -1,11 +1,10 @@
import React, { Component } from 'react';
import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react';
-import { Card, CardActions, PageSection } from '@patternfly/react-core';
+import { CaretLeftIcon } from '@patternfly/react-icons';
+import { Card, PageSection } from '@patternfly/react-core';
import { Switch, Route, Redirect, withRouter, Link } from 'react-router-dom';
-import { TabbedCardHeader } from '../../components/Card';
import AppendBody from '../../components/AppendBody';
-import CardCloseButton from '../../components/CardCloseButton';
import ContentError from '../../components/ContentError';
import FullPage from '../../components/FullPage';
import JobList from '../../components/JobList';
@@ -121,6 +120,16 @@ class WorkflowJobTemplate extends Component {
template?.summary_fields?.user_capabilities.delete;
const tabsArray = [
+ {
+ name: (
+ <>
+
+ {i18n._(t`Back to Templates`)}
+ >
+ ),
+ link: `/templates`,
+ id: 99,
+ },
{ name: i18n._(t`Details`), link: `${match.url}/details` },
{ name: i18n._(t`Access`), link: `${match.url}/access` },
];
@@ -183,22 +192,19 @@ class WorkflowJobTemplate extends Component {
);
}
- const cardHeader = (
-
-
-
-
-
-
- );
+ let showCardHeader = true;
+
+ if (
+ location.pathname.endsWith('edit') ||
+ location.pathname.includes('schedules/')
+ ) {
+ showCardHeader = false;
+ }
return (
- {location.pathname.endsWith('edit') ||
- location.pathname.includes('schedules/')
- ? null
- : cardHeader}
+ {showCardHeader && }
', () => {
const organization = wrapper
.find('Detail[label="Organization"]')
- .find('span');
+ .find('.pf-c-label__content');
const inventory = wrapper.find('Detail[label="Inventory"]').find('a');
const labels = wrapper
.find('Detail[label="Labels"]')
- .find('Chip[component="li"]');
+ .find('Chip[component="div"]');
const sparkline = wrapper.find('Sparkline Link');
expect(organization.text()).toBe('Org');
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.jsx
index 97a941755e..5d7baf4099 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.jsx
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkAddModal.jsx
@@ -1,5 +1,5 @@
import React, { useContext } from 'react';
-import { BaseSizes, Title, TitleLevel } from '@patternfly/react-core';
+import { Title } from '@patternfly/react-core';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { WorkflowDispatchContext } from '../../../../../contexts/Workflow';
@@ -10,7 +10,7 @@ function LinkAddModal({ i18n }) {
return (
+
{i18n._(t`Add Link`)}
}
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkEditModal.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkEditModal.jsx
index 177e11bb32..8c4259b73a 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkEditModal.jsx
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkEditModal.jsx
@@ -1,5 +1,5 @@
import React, { useContext } from 'react';
-import { BaseSizes, Title, TitleLevel } from '@patternfly/react-core';
+import { Title } from '@patternfly/react-core';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { WorkflowDispatchContext } from '../../../../../contexts/Workflow';
@@ -10,7 +10,7 @@ function LinkEditModal({ i18n }) {
return (
+
{i18n._(t`Edit Link`)}
}
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.jsx
index 0cf4601c4c..ed11bf59c2 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.jsx
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/LinkModals/LinkModal.jsx
@@ -20,8 +20,8 @@ function LinkModal({ header, i18n, onConfirm }) {
width={600}
header={header}
isOpen
- isFooterLeftAligned
title={i18n._(t`Workflow Link`)}
+ aria-label={i18n._(t`Workflow link modal`)}
onClose={() => dispatch({ type: 'CANCEL_LINK_MODAL' })}
actions={[
{
);
});
+ await waitForElement(wrapper, 'PFWizard');
});
afterAll(() => {
@@ -307,6 +311,7 @@ describe('NodeModal', () => {
);
});
+ await waitForElement(wrapper, 'PFWizard');
expect(wrapper.find('AnsibleSelect').prop('value')).toBe('project_sync');
await act(async () => {
wrapper.find('AnsibleSelect').prop('onChange')(null, 'approval');
@@ -388,6 +393,7 @@ describe('NodeModal', () => {
);
});
+ await waitForElement(wrapper, 'PFWizard');
expect(wrapper.find('AnsibleSelect').prop('value')).toBe('approval');
await act(async () => {
wrapper.find('AnsibleSelect').prop('onChange')(
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.jsx
index 146485d4cb..e9ee7928bb 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.jsx
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.jsx
@@ -131,14 +131,14 @@ function NodeTypeStep({
{
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.jsx
index 281c7b0368..6eb01a9a0e 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.jsx
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.jsx
@@ -131,10 +131,10 @@ function NodeViewModal({ i18n }) {
return (
dispatch({ type: 'SET_NODE_TO_VIEW', value: null })}
actions={[
dispatch({ type: 'TOGGLE_UNSAVED_CHANGES_MODAL' })}
actions={[
-
+
{template.name}
diff --git a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx
index 7bd4409fde..ca5ad56073 100644
--- a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx
+++ b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx
@@ -291,7 +291,9 @@ function JobTemplateForm({
@@ -381,7 +383,9 @@ function JobTemplateForm({
{
limitHelpers.setValue(value);
}}
diff --git a/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx b/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx
index d1f8de5ecc..539947738f 100644
--- a/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx
+++ b/awx/ui_next/src/screens/Template/shared/LabelSelect.jsx
@@ -83,8 +83,8 @@ function LabelSelect({ value, placeholder, onChange, onError, createText }) {
}}
isDisabled={isLoading}
selections={selections}
- isExpanded={isExpanded}
- ariaLabelledBy="label-select"
+ isOpen={isExpanded}
+ aria-labelledby="label-select"
placeholderText={placeholder}
createText={createText}
>
diff --git a/awx/ui_next/src/screens/UISetting/UISettings.jsx b/awx/ui_next/src/screens/UISetting/UISettings.jsx
index 1905463cd2..1ecec2af54 100644
--- a/awx/ui_next/src/screens/UISetting/UISettings.jsx
+++ b/awx/ui_next/src/screens/UISetting/UISettings.jsx
@@ -15,7 +15,9 @@ class UISettings extends Component {
return (
- {i18n._(t`User Interface Settings`)}
+
+ {i18n._(t`User Interface Settings`)}
+
diff --git a/awx/ui_next/src/screens/User/User.jsx b/awx/ui_next/src/screens/User/User.jsx
index 5e195da2f3..1aa8bca032 100644
--- a/awx/ui_next/src/screens/User/User.jsx
+++ b/awx/ui_next/src/screens/User/User.jsx
@@ -9,11 +9,10 @@ import {
useRouteMatch,
useLocation,
} from 'react-router-dom';
-import { Card, CardActions, PageSection } from '@patternfly/react-core';
+import { CaretLeftIcon } from '@patternfly/react-icons';
+import { Card, PageSection } from '@patternfly/react-core';
import useRequest from '../../util/useRequest';
import { UsersAPI } from '../../api';
-import { TabbedCardHeader } from '../../components/Card';
-import CardCloseButton from '../../components/CardCloseButton';
import ContentError from '../../components/ContentError';
import ContentLoading from '../../components/ContentLoading';
import RoutedTabs from '../../components/RoutedTabs';
@@ -52,6 +51,16 @@ function User({ i18n, setBreadcrumb }) {
}, [user, setBreadcrumb]);
const tabsArray = [
+ {
+ name: (
+ <>
+
+ {i18n._(t`Back to Users`)}
+ >
+ ),
+ link: `/users`,
+ id: 99,
+ },
{ name: i18n._(t`Details`), link: `${match.url}/details`, id: 0 },
{
name: i18n._(t`Organizations`),
@@ -63,6 +72,11 @@ function User({ i18n, setBreadcrumb }) {
{ name: i18n._(t`Tokens`), link: `${match.url}/tokens`, id: 4 },
];
+ let showCardHeader = true;
+ if (['edit'].some(name => location.pathname.includes(name))) {
+ showCardHeader = false;
+ }
+
if (contentError) {
return (
@@ -82,14 +96,7 @@ function User({ i18n, setBreadcrumb }) {
return (
- {['edit'].some(name => location.pathname.includes(name)) ? null : (
-
-
-
-
-
-
- )}
+ {showCardHeader && }
{isLoading && }
{!isLoading && user && (
diff --git a/awx/ui_next/src/screens/User/User.test.jsx b/awx/ui_next/src/screens/User/User.test.jsx
index 725247776d..ae3d951ceb 100644
--- a/awx/ui_next/src/screens/User/User.test.jsx
+++ b/awx/ui_next/src/screens/User/User.test.jsx
@@ -72,20 +72,10 @@ describe(' ', () => {
},
});
});
- await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 5);
+ await waitForElement(wrapper, '.pf-c-tabs__item', el => el.length === 6);
/* eslint-disable react/button-has-type */
- expect(
- wrapper
- .find('Tabs')
- .containsAllMatchingElements([
- Details ,
- Organizations ,
- Teams ,
- Access ,
- Tokens ,
- ])
- ).toEqual(true);
+ expect(wrapper.find('Tabs TabButton').length).toEqual(6);
});
test('should show content error when user attempts to navigate to erroneous route', async () => {
diff --git a/awx/ui_next/src/screens/User/UserAccess/UserAccessListItem.jsx b/awx/ui_next/src/screens/User/UserAccess/UserAccessListItem.jsx
index c06e4cca8d..524d07d438 100644
--- a/awx/ui_next/src/screens/User/UserAccess/UserAccessListItem.jsx
+++ b/awx/ui_next/src/screens/User/UserAccess/UserAccessListItem.jsx
@@ -40,12 +40,12 @@ function UserAccessListItem({ role, i18n, detailUrl, onSelect }) {
label={i18n._(t`Role`)}
value={
onSelect(role)}
+ isReadOnly={
+ !role.summary_fields.user_capabilities.unattach
+ }
>
{role.name}
diff --git a/awx/ui_next/src/screens/User/shared/UserForm.jsx b/awx/ui_next/src/screens/User/shared/UserForm.jsx
index 47294ff105..aa5229f296 100644
--- a/awx/ui_next/src/screens/User/shared/UserForm.jsx
+++ b/awx/ui_next/src/screens/User/shared/UserForm.jsx
@@ -117,7 +117,9 @@ function UserFormFields({ user, i18n }) {
fieldId="user-type"
helperTextInvalid={userTypeMeta.error}
isRequired
- isValid={!userTypeMeta.touched || !userTypeMeta.error}
+ validated={
+ !userTypeMeta.touched || !userTypeMeta.error ? 'default' : 'error'
+ }
label={i18n._(t`User Type`)}
>