diff --git a/awx/ui_next/src/components/AddRole/AddResourceRole.jsx b/awx/ui_next/src/components/AddRole/AddResourceRole.jsx
index 2f12953afa..e339142b52 100644
--- a/awx/ui_next/src/components/AddRole/AddResourceRole.jsx
+++ b/awx/ui_next/src/components/AddRole/AddResourceRole.jsx
@@ -1,4 +1,4 @@
-import React, { Fragment } from 'react';
+import React, { Fragment, useState } from 'react';
import PropTypes from 'prop-types';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
@@ -17,95 +17,57 @@ const readTeams = async queryParams => TeamsAPI.read(queryParams);
const readTeamsOptions = async () => TeamsAPI.readOptions();
-class AddResourceRole extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {
- selectedResource: null,
- selectedResourceRows: [],
- selectedRoleRows: [],
- currentStepId: 1,
- maxEnabledStep: 1,
- };
-
- this.handleResourceCheckboxClick = this.handleResourceCheckboxClick.bind(
- this
- );
- this.handleResourceSelect = this.handleResourceSelect.bind(this);
- this.handleRoleCheckboxClick = this.handleRoleCheckboxClick.bind(this);
- this.handleWizardNext = this.handleWizardNext.bind(this);
- this.handleWizardSave = this.handleWizardSave.bind(this);
- this.handleWizardGoToStep = this.handleWizardGoToStep.bind(this);
- }
-
- handleResourceCheckboxClick(user) {
- const { selectedResourceRows, currentStepId } = this.state;
+function AddResourceRole({ onSave, onClose, roles, i18n, resource }) {
+ const [selectedResource, setSelectedResource] = useState(null);
+ const [selectedResourceRows, setSelectedResourceRows] = useState([]);
+ const [selectedRoleRows, setSelectedRoleRows] = useState([]);
+ const [currentStepId, setCurrentStepId] = useState(1);
+ const [maxEnabledStep, setMaxEnabledStep] = useState(1);
+ const handleResourceCheckboxClick = user => {
const selectedIndex = selectedResourceRows.findIndex(
selectedRow => selectedRow.id === user.id
);
-
if (selectedIndex > -1) {
selectedResourceRows.splice(selectedIndex, 1);
- const stateToUpdate = { selectedResourceRows };
if (selectedResourceRows.length === 0) {
- stateToUpdate.maxEnabledStep = currentStepId;
+ setMaxEnabledStep(currentStepId);
}
- this.setState(stateToUpdate);
+ setSelectedRoleRows(selectedResourceRows);
} else {
- this.setState(prevState => ({
- selectedResourceRows: [...prevState.selectedResourceRows, user],
- }));
+ setSelectedResourceRows([...selectedResourceRows, user]);
}
- }
-
- handleRoleCheckboxClick(role) {
- const { selectedRoleRows } = this.state;
+ };
+ const handleRoleCheckboxClick = role => {
const selectedIndex = selectedRoleRows.findIndex(
selectedRow => selectedRow.id === role.id
);
if (selectedIndex > -1) {
selectedRoleRows.splice(selectedIndex, 1);
- this.setState({ selectedRoleRows });
+ setSelectedRoleRows(selectedRoleRows);
} else {
- this.setState(prevState => ({
- selectedRoleRows: [...prevState.selectedRoleRows, role],
- }));
+ setSelectedRoleRows([...selectedRoleRows, role]);
}
- }
+ };
- handleResourceSelect(resourceType) {
- this.setState({
- selectedResource: resourceType,
- selectedResourceRows: [],
- selectedRoleRows: [],
- });
- }
+ const handleResourceSelect = resourceType => {
+ setSelectedResource(resourceType);
+ setSelectedResourceRows([]);
+ setSelectedRoleRows([]);
+ };
- handleWizardNext(step) {
- this.setState({
- currentStepId: step.id,
- maxEnabledStep: step.id,
- });
- }
+ const handleWizardNext = step => {
+ setCurrentStepId(step.id);
+ setMaxEnabledStep(step.id);
+ };
- handleWizardGoToStep(step) {
- this.setState({
- currentStepId: step.id,
- });
- }
-
- async handleWizardSave() {
- const { onSave } = this.props;
- const {
- selectedResourceRows,
- selectedRoleRows,
- selectedResource,
- } = this.state;
+ const handleWizardGoToStep = step => {
+ setCurrentStepId(step.id);
+ };
+ const handleWizardSave = async () => {
try {
const roleRequests = [];
@@ -134,201 +96,186 @@ class AddResourceRole extends React.Component {
} catch (err) {
// TODO: handle this error
}
+ };
+
+ // Object roles can be user only, so we remove them when
+ // showing role choices for team access
+ const selectableRoles = { ...roles };
+ if (selectedResource === 'teams') {
+ Object.keys(roles).forEach(key => {
+ if (selectableRoles[key].user_only) {
+ delete selectableRoles[key];
+ }
+ });
}
- render() {
- const {
- selectedResource,
- selectedResourceRows,
- selectedRoleRows,
- currentStepId,
- maxEnabledStep,
- } = this.state;
- const { onClose, roles, i18n, resource } = this.props;
+ const userSearchColumns = [
+ {
+ name: i18n._(t`Username`),
+ key: 'username__icontains',
+ isDefault: true,
+ },
+ {
+ name: i18n._(t`First Name`),
+ key: 'first_name__icontains',
+ },
+ {
+ name: i18n._(t`Last Name`),
+ key: 'last_name__icontains',
+ },
+ ];
+ const userSortColumns = [
+ {
+ name: i18n._(t`Username`),
+ key: 'username',
+ },
+ {
+ name: i18n._(t`First Name`),
+ key: 'first_name',
+ },
+ {
+ name: i18n._(t`Last Name`),
+ key: 'last_name',
+ },
+ ];
+ const teamSearchColumns = [
+ {
+ name: i18n._(t`Name`),
+ key: 'name',
+ isDefault: true,
+ },
+ {
+ name: i18n._(t`Created By (Username)`),
+ key: 'created_by__username',
+ },
+ {
+ name: i18n._(t`Modified By (Username)`),
+ key: 'modified_by__username',
+ },
+ ];
- // Object roles can be user only, so we remove them when
- // showing role choices for team access
- const selectableRoles = { ...roles };
- if (selectedResource === 'teams') {
- Object.keys(roles).forEach(key => {
- if (selectableRoles[key].user_only) {
- delete selectableRoles[key];
- }
- });
- }
+ const teamSortColumns = [
+ {
+ name: i18n._(t`Name`),
+ key: 'name',
+ },
+ ];
- const userSearchColumns = [
- {
- name: i18n._(t`Username`),
- key: 'username__icontains',
- isDefault: true,
- },
- {
- name: i18n._(t`First Name`),
- key: 'first_name__icontains',
- },
- {
- name: i18n._(t`Last Name`),
- key: 'last_name__icontains',
- },
- ];
+ let wizardTitle = '';
- const userSortColumns = [
- {
- name: i18n._(t`Username`),
- key: 'username',
- },
- {
- name: i18n._(t`First Name`),
- key: 'first_name',
- },
- {
- name: i18n._(t`Last Name`),
- key: 'last_name',
- },
- ];
+ switch (selectedResource) {
+ case 'users':
+ wizardTitle = i18n._(t`Add User Roles`);
+ break;
+ case 'teams':
+ wizardTitle = i18n._(t`Add Team Roles`);
+ break;
+ default:
+ wizardTitle = i18n._(t`Add Roles`);
+ }
- const teamSearchColumns = [
- {
- name: i18n._(t`Name`),
- key: 'name',
- isDefault: true,
- },
- {
- name: i18n._(t`Created By (Username)`),
- key: 'created_by__username',
- },
- {
- name: i18n._(t`Modified By (Username)`),
- key: 'modified_by__username',
- },
- ];
-
- const teamSortColumns = [
- {
- name: i18n._(t`Name`),
- key: 'name',
- },
- ];
-
- let wizardTitle = '';
-
- switch (selectedResource) {
- case 'users':
- wizardTitle = i18n._(t`Add User Roles`);
- break;
- case 'teams':
- wizardTitle = i18n._(t`Add Team Roles`);
- break;
- default:
- wizardTitle = i18n._(t`Add Roles`);
- }
-
- const steps = [
- {
- id: 1,
- name: i18n._(t`Select a Resource Type`),
- component: (
-
-
- {i18n._(
- t`Choose the type of resource that will be receiving new roles. For example, if you'd like to add new roles to a set of users please choose Users and click Next. You'll be able to select the specific resources in the next step.`
- )}
-
-
-
this.handleResourceSelect('users')}
- />
- {resource?.type === 'credential' &&
- !resource?.organization ? null : (
- this.handleResourceSelect('teams')}
- />
+ const steps = [
+ {
+ id: 1,
+ name: i18n._(t`Select a Resource Type`),
+ component: (
+
+
+ {i18n._(
+ t`Choose the type of resource that will be receiving new roles. For example, if you'd like to add new roles to a set of users please choose Users and click Next. You'll be able to select the specific resources in the next step.`
)}
- ),
- enableNext: selectedResource !== null,
- },
- {
- id: 2,
- name: i18n._(t`Select Items from List`),
- component: (
-
- {selectedResource === 'users' && (
-
- )}
- {selectedResource === 'teams' && (
-
- )}
-
- ),
- enableNext: selectedResourceRows.length > 0,
- canJumpTo: maxEnabledStep >= 2,
- },
- {
- id: 3,
- name: i18n._(t`Select Roles to Apply`),
- component: (
-
handleResourceSelect('users')}
/>
- ),
- nextButtonText: i18n._(t`Save`),
- enableNext: selectedRoleRows.length > 0,
- canJumpTo: maxEnabledStep >= 3,
- },
- ];
+ {resource?.type === 'credential' && !resource?.organization ? null : (
+ handleResourceSelect('teams')}
+ />
+ )}
+
+ ),
+ enableNext: selectedResource !== null,
+ },
+ {
+ id: 2,
+ name: i18n._(t`Select Items from List`),
+ component: (
+
+ {selectedResource === 'users' && (
+
+ )}
+ {selectedResource === 'teams' && (
+
+ )}
+
+ ),
+ enableNext: selectedResourceRows.length > 0,
+ canJumpTo: maxEnabledStep >= 2,
+ },
+ {
+ id: 3,
+ name: i18n._(t`Select Roles to Apply`),
+ component: (
+
+ ),
+ nextButtonText: i18n._(t`Save`),
+ enableNext: selectedRoleRows.length > 0,
+ canJumpTo: maxEnabledStep >= 3,
+ },
+ ];
- const currentStep = steps.find(step => step.id === currentStepId);
+ const currentStep = steps.find(step => step.id === currentStepId);
- // TODO: somehow internationalize steps and currentStep.nextButtonText
- return (
-
- );
- }
+ // TODO: somehow internationalize steps and currentStep.nextButtonText
+ return (
+ handleWizardGoToStep(step)}
+ steps={steps}
+ title={wizardTitle}
+ nextButtonText={currentStep.nextButtonText || undefined}
+ backButtonText={i18n._(t`Back`)}
+ cancelButtonText={i18n._(t`Cancel`)}
+ />
+ );
}
AddResourceRole.propTypes = {
diff --git a/awx/ui_next/src/components/AddRole/AddResourceRole.test.jsx b/awx/ui_next/src/components/AddRole/AddResourceRole.test.jsx
index a681999391..264f7cdb28 100644
--- a/awx/ui_next/src/components/AddRole/AddResourceRole.test.jsx
+++ b/awx/ui_next/src/components/AddRole/AddResourceRole.test.jsx
@@ -1,22 +1,46 @@
/* eslint-disable react/jsx-pascal-case */
import React from 'react';
import { shallow } from 'enzyme';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
+import { act } from 'react-dom/test-utils';
+
+import {
+ mountWithContexts,
+ waitForElement,
+} from '../../../testUtils/enzymeHelpers';
import AddResourceRole, { _AddResourceRole } from './AddResourceRole';
import { TeamsAPI, UsersAPI } from '../../api';
-jest.mock('../../api');
+jest.mock('../../api/models/Teams');
+jest.mock('../../api/models/Users');
+
+// TODO: Once error handling is functional in
+// this component write tests for it
describe('<_AddResourceRole />', () => {
UsersAPI.read.mockResolvedValue({
data: {
count: 2,
results: [
- { id: 1, username: 'foo' },
- { id: 2, username: 'bar' },
+ { id: 1, username: 'foo', url: '' },
+ { id: 2, username: 'bar', url: '' },
],
},
});
+ UsersAPI.readOptions.mockResolvedValue({
+ data: { related: {}, actions: { GET: {} } },
+ });
+ TeamsAPI.read.mockResolvedValue({
+ data: {
+ count: 2,
+ results: [
+ { id: 1, name: 'Team foo', url: '' },
+ { id: 2, name: 'Team bar', url: '' },
+ ],
+ },
+ });
+ TeamsAPI.readOptions.mockResolvedValue({
+ data: { related: {}, actions: { GET: {} } },
+ });
const roles = {
admin_role: {
description: 'Can manage all aspects of the organization',
@@ -39,191 +63,165 @@ describe('<_AddResourceRole />', () => {
/>
);
});
- test('handleRoleCheckboxClick properly updates state', () => {
- const wrapper = shallow(
- <_AddResourceRole
- onClose={() => {}}
- onSave={() => {}}
- roles={roles}
- i18n={{ _: val => val.toString() }}
- />
- );
- wrapper.setState({
- selectedRoleRows: [
- {
- description: 'Can manage all aspects of the organization',
- name: 'Admin',
- id: 1,
- },
- ],
+ test('should save properly', async () => {
+ let wrapper;
+ act(() => {
+ wrapper = mountWithContexts(
+ {}} onSave={() => {}} roles={roles} />,
+ { context: { network: { handleHttpError: () => {} } } }
+ );
});
- wrapper.instance().handleRoleCheckboxClick({
- description: 'Can manage all aspects of the organization',
- name: 'Admin',
- id: 1,
- });
- expect(wrapper.state('selectedRoleRows')).toEqual([]);
- wrapper.instance().handleRoleCheckboxClick({
- description: 'Can manage all aspects of the organization',
- name: 'Admin',
- id: 1,
- });
- expect(wrapper.state('selectedRoleRows')).toEqual([
- {
- description: 'Can manage all aspects of the organization',
- name: 'Admin',
- id: 1,
- },
- ]);
- });
- test('handleResourceCheckboxClick properly updates state', () => {
- const wrapper = shallow(
- <_AddResourceRole
- onClose={() => {}}
- onSave={() => {}}
- roles={roles}
- i18n={{ _: val => val.toString() }}
- />
- );
- wrapper.setState({
- selectedResourceRows: [
- {
- id: 1,
- username: 'foobar',
- },
- ],
- });
- wrapper.instance().handleResourceCheckboxClick({
- id: 1,
- username: 'foobar',
- });
- expect(wrapper.state('selectedResourceRows')).toEqual([]);
- wrapper.instance().handleResourceCheckboxClick({
- id: 1,
- username: 'foobar',
- });
- expect(wrapper.state('selectedResourceRows')).toEqual([
- {
- id: 1,
- username: 'foobar',
- },
- ]);
- });
- test('clicking user/team cards updates state', () => {
- const spy = jest.spyOn(_AddResourceRole.prototype, 'handleResourceSelect');
- const wrapper = mountWithContexts(
- {}} onSave={() => {}} roles={roles} />,
- { context: { network: { handleHttpError: () => {} } } }
- ).find('AddResourceRole');
+ wrapper.update();
+
+ // Step 1
const selectableCardWrapper = wrapper.find('SelectableCard');
expect(selectableCardWrapper.length).toBe(2);
- selectableCardWrapper.first().simulate('click');
- expect(spy).toHaveBeenCalledWith('users');
- expect(wrapper.state('selectedResource')).toBe('users');
- selectableCardWrapper.at(1).simulate('click');
- expect(spy).toHaveBeenCalledWith('teams');
- expect(wrapper.state('selectedResource')).toBe('teams');
- });
- test('handleResourceSelect clears out selected lists and sets selectedResource', () => {
- const wrapper = shallow(
- <_AddResourceRole
- onClose={() => {}}
- onSave={() => {}}
- roles={roles}
- i18n={{ _: val => val.toString() }}
- />
+ act(() => wrapper.find('SelectableCard[label="Users"]').prop('onClick')());
+ wrapper.update();
+ await act(async () =>
+ wrapper.find('Button[type="submit"]').prop('onClick')()
);
- wrapper.setState({
- selectedResource: 'teams',
- selectedResourceRows: [
- {
- id: 1,
- username: 'foobar',
- },
- ],
- selectedRoleRows: [
- {
- description: 'Can manage all aspects of the organization',
- id: 1,
- name: 'Admin',
- },
- ],
- });
- wrapper.instance().handleResourceSelect('users');
- expect(wrapper.state()).toEqual({
- selectedResource: 'users',
- selectedResourceRows: [],
- selectedRoleRows: [],
- currentStepId: 1,
- maxEnabledStep: 1,
- });
- wrapper.instance().handleResourceSelect('teams');
- expect(wrapper.state()).toEqual({
- selectedResource: 'teams',
- selectedResourceRows: [],
- selectedRoleRows: [],
- currentStepId: 1,
- maxEnabledStep: 1,
- });
+ wrapper.update();
+
+ // Step 2
+ await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
+ act(() =>
+ wrapper.find('DataListCheck[name="foo"]').invoke('onChange')(true)
+ );
+ wrapper.update();
+ expect(wrapper.find('DataListCheck[name="foo"]').prop('checked')).toBe(
+ true
+ );
+ act(() => wrapper.find('Button[type="submit"]').prop('onClick')());
+ wrapper.update();
+
+ // Step 3
+ act(() =>
+ wrapper.find('Checkbox[aria-label="Admin"]').invoke('onChange')(true)
+ );
+ wrapper.update();
+ expect(wrapper.find('Checkbox[aria-label="Admin"]').prop('isChecked')).toBe(
+ true
+ );
+
+ // Save
+ await act(async () =>
+ wrapper.find('Button[type="submit"]').prop('onClick')()
+ );
+ expect(UsersAPI.associateRole).toBeCalledWith(1, 1);
});
- test('handleWizardSave makes correct api calls, calls onSave when done', async () => {
- const handleSave = jest.fn();
- const wrapper = mountWithContexts(
- {}} onSave={handleSave} roles={roles} />,
- { context: { network: { handleHttpError: () => {} } } }
- ).find('AddResourceRole');
- wrapper.setState({
- selectedResource: 'users',
- selectedResourceRows: [
- {
- id: 1,
- username: 'foobar',
- },
- ],
- selectedRoleRows: [
- {
- description: 'Can manage all aspects of the organization',
- id: 1,
- name: 'Admin',
- },
- {
- description: 'May run any executable resources in the organization',
- id: 2,
- name: 'Execute',
- },
- ],
+
+ test('should successfuly click user/team cards', async () => {
+ let wrapper;
+ act(() => {
+ wrapper = mountWithContexts(
+ {}} onSave={() => {}} roles={roles} />,
+ { context: { network: { handleHttpError: () => {} } } }
+ );
});
- await wrapper.instance().handleWizardSave();
- expect(UsersAPI.associateRole).toHaveBeenCalledTimes(2);
- expect(handleSave).toHaveBeenCalled();
- wrapper.setState({
- selectedResource: 'teams',
- selectedResourceRows: [
- {
- id: 1,
- name: 'foobar',
- },
- ],
- selectedRoleRows: [
- {
- description: 'Can manage all aspects of the organization',
- id: 1,
- name: 'Admin',
- },
- {
- description: 'May run any executable resources in the organization',
- id: 2,
- name: 'Execute',
- },
- ],
+ wrapper.update();
+
+ const selectableCardWrapper = wrapper.find('SelectableCard');
+ expect(selectableCardWrapper.length).toBe(2);
+ act(() => wrapper.find('SelectableCard[label="Users"]').prop('onClick')());
+ wrapper.update();
+
+ await waitForElement(
+ wrapper,
+ 'SelectableCard[label="Users"]',
+ el => el.prop('isSelected') === true
+ );
+ act(() => wrapper.find('SelectableCard[label="Teams"]').prop('onClick')());
+ wrapper.update();
+
+ await waitForElement(
+ wrapper,
+ 'SelectableCard[label="Teams"]',
+ el => el.prop('isSelected') === true
+ );
+ });
+
+ test('should reset values with resource type changes', async () => {
+ let wrapper;
+ act(() => {
+ wrapper = mountWithContexts(
+ {}} onSave={() => {}} roles={roles} />,
+ { context: { network: { handleHttpError: () => {} } } }
+ );
});
- await wrapper.instance().handleWizardSave();
- expect(TeamsAPI.associateRole).toHaveBeenCalledTimes(2);
- expect(handleSave).toHaveBeenCalled();
+ wrapper.update();
+
+ // Step 1
+ const selectableCardWrapper = wrapper.find('SelectableCard');
+ expect(selectableCardWrapper.length).toBe(2);
+ act(() => wrapper.find('SelectableCard[label="Users"]').prop('onClick')());
+ wrapper.update();
+ await act(async () =>
+ wrapper.find('Button[type="submit"]').prop('onClick')()
+ );
+ wrapper.update();
+
+ // Step 2
+ await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
+ act(() =>
+ wrapper.find('DataListCheck[name="foo"]').invoke('onChange')(true)
+ );
+ wrapper.update();
+ expect(wrapper.find('DataListCheck[name="foo"]').prop('checked')).toBe(
+ true
+ );
+ act(() => wrapper.find('Button[type="submit"]').prop('onClick')());
+ wrapper.update();
+
+ // Step 3
+ act(() =>
+ wrapper.find('Checkbox[aria-label="Admin"]').invoke('onChange')(true)
+ );
+ wrapper.update();
+ expect(wrapper.find('Checkbox[aria-label="Admin"]').prop('isChecked')).toBe(
+ true
+ );
+
+ // Go back to step 1
+ act(() => {
+ wrapper
+ .find('WizardNavItem[content="Select a Resource Type"]')
+ .find('button')
+ .prop('onClick')({ id: 1 });
+ });
+ wrapper.update();
+ expect(
+ wrapper
+ .find('WizardNavItem[content="Select a Resource Type"]')
+ .prop('isCurrent')
+ ).toBe(true);
+
+ // Go back to step 1 and this time select teams. Doing so should clear following steps
+ act(() => wrapper.find('SelectableCard[label="Teams"]').prop('onClick')());
+ wrapper.update();
+ await act(async () =>
+ wrapper.find('Button[type="submit"]').prop('onClick')()
+ );
+ wrapper.update();
+
+ // Make sure no teams have been selected
+ await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
+ wrapper
+ .find('DataListCheck')
+ .map(item => expect(item.prop('checked')).toBe(false));
+ act(() => wrapper.find('Button[type="submit"]').prop('onClick')());
+ wrapper.update();
+
+ // Make sure that no roles have been selected
+ wrapper
+ .find('Checkbox')
+ .map(card => expect(card.prop('isChecked')).toBe(false));
+
+ // Make sure the save button is disabled
+ expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe(true);
});
test('should not display team as a choice in case credential does not have organization', () => {
- const spy = jest.spyOn(_AddResourceRole.prototype, 'handleResourceSelect');
const wrapper = mountWithContexts(
{}}
@@ -232,11 +230,13 @@ describe('<_AddResourceRole />', () => {
resource={{ type: 'credential', organization: null }}
/>,
{ context: { network: { handleHttpError: () => {} } } }
- ).find('AddResourceRole');
- const selectableCardWrapper = wrapper.find('SelectableCard');
- expect(selectableCardWrapper.length).toBe(1);
- selectableCardWrapper.first().simulate('click');
- expect(spy).toHaveBeenCalledWith('users');
- expect(wrapper.state('selectedResource')).toBe('users');
+ );
+
+ expect(wrapper.find('SelectableCard').length).toBe(1);
+ wrapper.find('SelectableCard[label="Users"]').simulate('click');
+ wrapper.update();
+ expect(
+ wrapper.find('SelectableCard[label="Users"]').prop('isSelected')
+ ).toBe(true);
});
});
diff --git a/awx/ui_next/src/components/AddRole/SelectRoleStep.jsx b/awx/ui_next/src/components/AddRole/SelectRoleStep.jsx
index 826c2d52aa..32f0e6a96c 100644
--- a/awx/ui_next/src/components/AddRole/SelectRoleStep.jsx
+++ b/awx/ui_next/src/components/AddRole/SelectRoleStep.jsx
@@ -7,59 +7,55 @@ import { t } from '@lingui/macro';
import CheckboxCard from './CheckboxCard';
import SelectedList from '../SelectedList';
-class RolesStep extends React.Component {
- render() {
- const {
- onRolesClick,
- roles,
- selectedListKey,
- selectedListLabel,
- selectedResourceRows,
- selectedRoleRows,
- i18n,
- } = this.props;
-
- return (
-
-
- {i18n._(
- t`Choose roles to apply to the selected resources. Note that all selected roles will be applied to all selected resources.`
- )}
-
-
- {selectedResourceRows.length > 0 && (
-
- )}
-
-
- {Object.keys(roles).map(role => (
- item.id === roles[role].id
- )}
- key={roles[role].id}
- name={roles[role].name}
- onSelect={() => onRolesClick(roles[role])}
- />
- ))}
-
-
- );
- }
+function RolesStep({
+ onRolesClick,
+ roles,
+ selectedListKey,
+ selectedListLabel,
+ selectedResourceRows,
+ selectedRoleRows,
+ i18n,
+}) {
+ return (
+
+
+ {i18n._(
+ t`Choose roles to apply to the selected resources. Note that all selected roles will be applied to all selected resources.`
+ )}
+
+
+ {selectedResourceRows.length > 0 && (
+
+ )}
+
+
+ {Object.keys(roles).map(role => (
+ item.id === roles[role].id
+ )}
+ key={roles[role].id}
+ name={roles[role].name}
+ onSelect={() => onRolesClick(roles[role])}
+ />
+ ))}
+
+
+ );
}
RolesStep.propTypes = {
diff --git a/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.jsx b/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.jsx
index 4f49268c0a..62b8983eb4 100644
--- a/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.jsx
+++ b/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.jsx
@@ -12,52 +12,44 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { FormSelect, FormSelectOption } from '@patternfly/react-core';
-class AnsibleSelect extends React.Component {
- constructor(props) {
- super(props);
- this.onSelectChange = this.onSelectChange.bind(this);
- }
-
- onSelectChange(val, event) {
- const { onChange, name } = this.props;
+function AnsibleSelect({
+ id,
+ data,
+ i18n,
+ isValid,
+ onBlur,
+ value,
+ className,
+ isDisabled,
+ onChange,
+ name,
+}) {
+ const onSelectChange = (val, event) => {
event.target.name = name;
onChange(event, val);
- }
+ };
- render() {
- const {
- id,
- data,
- i18n,
- isValid,
- onBlur,
- value,
- className,
- isDisabled,
- } = this.props;
-
- return (
-
- {data.map(option => (
-
- ))}
-
- );
- }
+ return (
+
+ {data.map(option => (
+
+ ))}
+
+ );
}
const Option = shape({
diff --git a/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.test.jsx b/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.test.jsx
index ced058754a..bb671c8823 100644
--- a/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.test.jsx
+++ b/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.test.jsx
@@ -1,6 +1,6 @@
import React from 'react';
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
-import AnsibleSelect, { _AnsibleSelect } from './AnsibleSelect';
+import AnsibleSelect from './AnsibleSelect';
const mockData = [
{
@@ -16,6 +16,7 @@ const mockData = [
];
describe(' ', () => {
+ const onChange = jest.fn();
test('initially renders succesfully', async () => {
mountWithContexts(
', () => {
});
test('calls "onSelectChange" on dropdown select change', () => {
- const spy = jest.spyOn(_AnsibleSelect.prototype, 'onSelectChange');
const wrapper = mountWithContexts(
{}}
+ onChange={onChange}
data={mockData}
/>
);
- expect(spy).not.toHaveBeenCalled();
+ expect(onChange).not.toHaveBeenCalled();
wrapper.find('select').simulate('change');
- expect(spy).toHaveBeenCalled();
+ expect(onChange).toHaveBeenCalled();
});
test('Returns correct select options', () => {
diff --git a/awx/ui_next/src/components/ExpandCollapse/ExpandCollapse.jsx b/awx/ui_next/src/components/ExpandCollapse/ExpandCollapse.jsx
index 7ffce947d8..00a33b68c2 100644
--- a/awx/ui_next/src/components/ExpandCollapse/ExpandCollapse.jsx
+++ b/awx/ui_next/src/components/ExpandCollapse/ExpandCollapse.jsx
@@ -31,35 +31,31 @@ const ToolbarItem = styled(PFToolbarItem)`
// TODO: Recommend renaming this component to avoid confusion
// with ExpandingContainer
-class ExpandCollapse extends React.Component {
- render() {
- const { isCompact, onCompact, onExpand, i18n } = this.props;
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
+function ExpandCollapse({ isCompact, onCompact, onExpand, i18n }) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
}
ExpandCollapse.propTypes = {
diff --git a/awx/ui_next/src/components/ResourceAccessList/DeleteRoleConfirmationModal.jsx b/awx/ui_next/src/components/ResourceAccessList/DeleteRoleConfirmationModal.jsx
index 8ac2f79bc4..407ebdb499 100644
--- a/awx/ui_next/src/components/ResourceAccessList/DeleteRoleConfirmationModal.jsx
+++ b/awx/ui_next/src/components/ResourceAccessList/DeleteRoleConfirmationModal.jsx
@@ -7,69 +7,71 @@ import { t } from '@lingui/macro';
import AlertModal from '../AlertModal';
import { Role } from '../../types';
-class DeleteRoleConfirmationModal extends React.Component {
- static propTypes = {
- role: Role.isRequired,
- username: string,
- onCancel: func.isRequired,
- onConfirm: func.isRequired,
- };
-
- static defaultProps = {
- username: '',
- };
-
- isTeamRole() {
- const { role } = this.props;
+function DeleteRoleConfirmationModal({
+ role,
+ username,
+ onCancel,
+ onConfirm,
+ i18n,
+}) {
+ const isTeamRole = () => {
return typeof role.team_id !== 'undefined';
- }
+ };
- render() {
- const { role, username, onCancel, onConfirm, i18n } = this.props;
- const title = i18n._(
- t`Remove ${this.isTeamRole() ? i18n._(t`Team`) : i18n._(t`User`)} Access`
- );
- return (
-
- {i18n._(t`Delete`)}
- ,
-
- {i18n._(t`Cancel`)}
- ,
- ]}
- >
- {this.isTeamRole() ? (
-
- {i18n._(
- t`Are you sure you want to remove ${role.name} access from ${role.team_name}? Doing so affects all members of the team.`
- )}
-
-
- {i18n._(
- t`If you only want to remove access for this particular user, please remove them from the team.`
- )}
-
- ) : (
-
- {i18n._(
- t`Are you sure you want to remove ${role.name} access from ${username}?`
- )}
-
- )}
-
- );
- }
+ const title = i18n._(
+ t`Remove ${isTeamRole() ? i18n._(t`Team`) : i18n._(t`User`)} Access`
+ );
+ return (
+
+ {i18n._(t`Delete`)}
+ ,
+
+ {i18n._(t`Cancel`)}
+ ,
+ ]}
+ >
+ {isTeamRole() ? (
+
+ {i18n._(
+ t`Are you sure you want to remove ${role.name} access from ${role.team_name}? Doing so affects all members of the team.`
+ )}
+
+
+ {i18n._(
+ t`If you only want to remove access for this particular user, please remove them from the team.`
+ )}
+
+ ) : (
+
+ {i18n._(
+ t`Are you sure you want to remove ${role.name} access from ${username}?`
+ )}
+
+ )}
+
+ );
}
+DeleteRoleConfirmationModal.propTypes = {
+ role: Role.isRequired,
+ username: string,
+ onCancel: func.isRequired,
+ onConfirm: func.isRequired,
+};
+
+DeleteRoleConfirmationModal.defaultProps = {
+ username: '',
+};
+
export default withI18n()(DeleteRoleConfirmationModal);
diff --git a/awx/ui_next/src/components/ResourceAccessList/ResourceAccessList.jsx b/awx/ui_next/src/components/ResourceAccessList/ResourceAccessList.jsx
index b5b1765d45..0f5f7c1c64 100644
--- a/awx/ui_next/src/components/ResourceAccessList/ResourceAccessList.jsx
+++ b/awx/ui_next/src/components/ResourceAccessList/ResourceAccessList.jsx
@@ -144,6 +144,7 @@ function ResourceAccessList({ i18n, apiModel, resource }) {
setDeletionRole(role);
setShowDeleteModal(true);
}}
+ i18n={i18n}
/>
)}
/>
diff --git a/awx/ui_next/src/components/ResourceAccessList/ResourceAccessListItem.jsx b/awx/ui_next/src/components/ResourceAccessList/ResourceAccessListItem.jsx
index 3fe656a3fb..d641e67e01 100644
--- a/awx/ui_next/src/components/ResourceAccessList/ResourceAccessListItem.jsx
+++ b/awx/ui_next/src/components/ResourceAccessList/ResourceAccessListItem.jsx
@@ -24,19 +24,13 @@ const DataListItemCells = styled(PFDataListItemCells)`
align-items: start;
`;
-class ResourceAccessListItem extends React.Component {
- static propTypes = {
+function ResourceAccessListItem({ accessRecord, onRoleDelete, i18n }) {
+ ResourceAccessListItem.propTypes = {
accessRecord: AccessRecord.isRequired,
onRoleDelete: func.isRequired,
};
- constructor(props) {
- super(props);
- this.renderChip = this.renderChip.bind(this);
- }
-
- getRoleLists() {
- const { accessRecord } = this.props;
+ const getRoleLists = () => {
const teamRoles = [];
const userRoles = [];
@@ -52,10 +46,9 @@ class ResourceAccessListItem extends React.Component {
accessRecord.summary_fields.direct_access.map(sort);
accessRecord.summary_fields.indirect_access.map(sort);
return [teamRoles, userRoles];
- }
+ };
- renderChip(role) {
- const { accessRecord, onRoleDelete } = this.props;
+ const renderChip = role => {
return (
);
- }
+ };
- render() {
- const { accessRecord, i18n } = this.props;
- const [teamRoles, userRoles] = this.getRoleLists();
+ const [teamRoles, userRoles] = getRoleLists();
- return (
-
-
-
- {accessRecord.username && (
-
- {accessRecord.id ? (
-
-
- {accessRecord.username}
-
-
- ) : (
-
+ return (
+
+
+
+ {accessRecord.username && (
+
+ {accessRecord.id ? (
+
+
{accessRecord.username}
-
- )}
-
- )}
- {accessRecord.first_name || accessRecord.last_name ? (
-
-
-
- ) : null}
- ,
-
+
+
+ ) : (
+
+ {accessRecord.username}
+
+ )}
+
+ )}
+ {accessRecord.first_name || accessRecord.last_name ? (
- {userRoles.length > 0 && (
-
- {userRoles.map(this.renderChip)}
-
- }
- />
- )}
- {teamRoles.length > 0 && (
-
- {teamRoles.map(this.renderChip)}
-
- }
- />
- )}
+
- ,
- ]}
- />
-
-
- );
- }
+ ) : null}
+ ,
+
+
+ {userRoles.length > 0 && (
+
+ {userRoles.map(renderChip)}
+
+ }
+ />
+ )}
+ {teamRoles.length > 0 && (
+
+ {teamRoles.map(renderChip)}
+
+ }
+ />
+ )}
+
+ ,
+ ]}
+ />
+
+
+ );
}
export default withI18n()(ResourceAccessListItem);
diff --git a/awx/ui_next/src/components/Sort/Sort.jsx b/awx/ui_next/src/components/Sort/Sort.jsx
index c0a513cd48..ee8599b531 100644
--- a/awx/ui_next/src/components/Sort/Sort.jsx
+++ b/awx/ui_next/src/components/Sort/Sort.jsx
@@ -1,7 +1,7 @@
-import React, { Fragment } from 'react';
+import React, { Fragment, useState } from 'react';
import PropTypes from 'prop-types';
import { withI18n } from '@lingui/react';
-import { withRouter } from 'react-router-dom';
+import { useLocation, withRouter } from 'react-router-dom';
import { t } from '@lingui/macro';
import {
Button,
@@ -31,140 +31,110 @@ const NoOptionDropdown = styled.div`
border-bottom-color: var(--pf-global--BorderColor--200);
`;
-class Sort extends React.Component {
- constructor(props) {
- super(props);
+function Sort({ columns, qsConfig, onSort, i18n }) {
+ const location = useLocation();
+ const [isSortDropdownOpen, setIsSortDropdownOpen] = useState(false);
- let sortKey;
- let sortOrder;
- let isNumeric;
+ let sortKey;
+ let sortOrder;
+ let isNumeric;
- const { qsConfig, location } = this.props;
- const queryParams = parseQueryString(qsConfig, location.search);
- if (queryParams.order_by && queryParams.order_by.startsWith('-')) {
- sortKey = queryParams.order_by.substr(1);
- sortOrder = 'descending';
- } else if (queryParams.order_by) {
- sortKey = queryParams.order_by;
- sortOrder = 'ascending';
- }
-
- if (qsConfig.integerFields.find(field => field === sortKey)) {
- isNumeric = true;
- } else {
- isNumeric = false;
- }
-
- this.state = {
- isSortDropdownOpen: false,
- sortKey,
- sortOrder,
- isNumeric,
- };
-
- this.handleDropdownToggle = this.handleDropdownToggle.bind(this);
- this.handleDropdownSelect = this.handleDropdownSelect.bind(this);
- this.handleSort = this.handleSort.bind(this);
+ const queryParams = parseQueryString(qsConfig, location.search);
+ if (queryParams.order_by && queryParams.order_by.startsWith('-')) {
+ sortKey = queryParams.order_by.substr(1);
+ sortOrder = 'descending';
+ } else if (queryParams.order_by) {
+ sortKey = queryParams.order_by;
+ sortOrder = 'ascending';
}
- handleDropdownToggle(isSortDropdownOpen) {
- this.setState({ isSortDropdownOpen });
+ if (qsConfig.integerFields.find(field => field === sortKey)) {
+ isNumeric = true;
+ } else {
+ isNumeric = false;
}
- handleDropdownSelect({ target }) {
- const { columns, onSort, qsConfig } = this.props;
- const { sortOrder } = this.state;
+ const handleDropdownToggle = isOpen => {
+ setIsSortDropdownOpen(isOpen);
+ };
+
+ const handleDropdownSelect = ({ target }) => {
const { innerText } = target;
- const [{ key: sortKey }] = columns.filter(({ name }) => name === innerText);
-
- let isNumeric;
-
- if (qsConfig.integerFields.find(field => field === sortKey)) {
+ const [{ key }] = columns.filter(({ name }) => name === innerText);
+ sortKey = key;
+ if (qsConfig.integerFields.find(field => field === key)) {
isNumeric = true;
} else {
isNumeric = false;
}
- this.setState({ isSortDropdownOpen: false, sortKey, isNumeric });
+ setIsSortDropdownOpen(false);
onSort(sortKey, sortOrder);
- }
+ };
- handleSort() {
- const { onSort } = this.props;
- const { sortKey, sortOrder } = this.state;
- const newSortOrder = sortOrder === 'ascending' ? 'descending' : 'ascending';
- this.setState({ sortOrder: newSortOrder });
- onSort(sortKey, newSortOrder);
- }
+ const handleSort = () => {
+ onSort(sortKey, sortOrder === 'ascending' ? 'descending' : 'ascending');
+ };
- render() {
- const { up } = DropdownPosition;
- const { columns, i18n } = this.props;
- const { isSortDropdownOpen, sortKey, sortOrder, isNumeric } = this.state;
+ const { up } = DropdownPosition;
- const defaultSortedColumn = columns.find(({ key }) => key === sortKey);
+ const defaultSortedColumn = columns.find(({ key }) => key === sortKey);
- if (!defaultSortedColumn) {
- throw new Error(
- 'sortKey must match one of the column keys, check the sortColumns prop passed to '
- );
- }
-
- const sortedColumnName = defaultSortedColumn?.name;
-
- const sortDropdownItems = columns
- .filter(({ key }) => key !== sortKey)
- .map(({ key, name }) => (
-
- {name}
-
- ));
-
- let SortIcon;
- if (isNumeric) {
- SortIcon =
- sortOrder === 'ascending'
- ? SortNumericDownIcon
- : SortNumericDownAltIcon;
- } else {
- SortIcon =
- sortOrder === 'ascending' ? SortAlphaDownIcon : SortAlphaDownAltIcon;
- }
-
- return (
-
- {sortedColumnName && (
-
- {(sortDropdownItems.length > 0 && (
-
- {sortedColumnName}
-
- }
- dropdownItems={sortDropdownItems}
- />
- )) || {sortedColumnName} }
-
-
-
-
- )}
-
+ if (!defaultSortedColumn) {
+ throw new Error(
+ 'sortKey must match one of the column keys, check the sortColumns prop passed to '
);
}
+
+ const sortedColumnName = defaultSortedColumn?.name;
+
+ const sortDropdownItems = columns
+ .filter(({ key }) => key !== sortKey)
+ .map(({ key, name }) => (
+
+ {name}
+
+ ));
+
+ let SortIcon;
+ if (isNumeric) {
+ SortIcon =
+ sortOrder === 'ascending' ? SortNumericDownIcon : SortNumericDownAltIcon;
+ } else {
+ SortIcon =
+ sortOrder === 'ascending' ? SortAlphaDownIcon : SortAlphaDownAltIcon;
+ }
+ return (
+
+ {sortedColumnName && (
+
+ {(sortDropdownItems.length > 0 && (
+
+ {sortedColumnName}
+
+ }
+ dropdownItems={sortDropdownItems}
+ />
+ )) || {sortedColumnName} }
+
+
+
+
+
+ )}
+
+ );
}
Sort.propTypes = {
diff --git a/awx/ui_next/src/components/Sort/Sort.test.jsx b/awx/ui_next/src/components/Sort/Sort.test.jsx
index 1764cf0298..c6cf89ad0d 100644
--- a/awx/ui_next/src/components/Sort/Sort.test.jsx
+++ b/awx/ui_next/src/components/Sort/Sort.test.jsx
@@ -1,5 +1,10 @@
import React from 'react';
-import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
+import { act } from 'react-dom/test-utils';
+import {
+ mountWithContexts,
+ waitForElement,
+} from '../../../testUtils/enzymeHelpers';
+
import Sort from './Sort';
describe(' ', () => {
@@ -105,7 +110,7 @@ describe(' ', () => {
expect(onSort).toHaveBeenCalledWith('foo', 'ascending');
});
- test('Changing dropdown correctly passes back new sort key', () => {
+ test('Changing dropdown correctly passes back new sort key', async () => {
const qsConfig = {
namespace: 'item',
defaultParams: { page: 1, page_size: 5, order_by: 'foo' },
@@ -131,44 +136,18 @@ describe(' ', () => {
const wrapper = mountWithContexts(
- ).find('Sort');
-
- wrapper.instance().handleDropdownSelect({ target: { innerText: 'Bar' } });
+ );
+ act(() => wrapper.find('Dropdown').invoke('onToggle')(true));
+ wrapper.update();
+ await waitForElement(wrapper, 'Dropdown', el => el.prop('isOpen') === true);
+ wrapper
+ .find('li')
+ .at(0)
+ .prop('onClick')({ target: { innerText: 'Bar' } });
+ wrapper.update();
expect(onSort).toBeCalledWith('bar', 'ascending');
});
- test('Opening dropdown correctly updates state', () => {
- const qsConfig = {
- namespace: 'item',
- defaultParams: { page: 1, page_size: 5, order_by: 'foo' },
- integerFields: ['page', 'page_size'],
- };
-
- const columns = [
- {
- name: 'Foo',
- key: 'foo',
- },
- {
- name: 'Bar',
- key: 'bar',
- },
- {
- name: 'Bakery',
- key: 'bakery',
- },
- ];
-
- const onSort = jest.fn();
-
- const wrapper = mountWithContexts(
-
- ).find('Sort');
- expect(wrapper.state('isSortDropdownOpen')).toEqual(false);
- wrapper.instance().handleDropdownToggle(true);
- expect(wrapper.state('isSortDropdownOpen')).toEqual(true);
- });
-
test('It displays correct sort icon', () => {
const forwardNumericIconSelector = 'SortNumericDownIcon';
const reverseNumericIconSelector = 'SortNumericDownAltIcon';
diff --git a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
index 2bc991b969..88e32be49f 100644
--- a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
+++ b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
@@ -1,6 +1,6 @@
import React, { Component, Fragment } from 'react';
import { withRouter } from 'react-router-dom';
-import { withI18n } from '@lingui/react';
+import { I18n } from '@lingui/react';
import { t } from '@lingui/macro';
import styled from 'styled-components';
import {
@@ -518,7 +518,7 @@ class JobOutput extends Component {
}
render() {
- const { job, i18n } = this.props;
+ const { job } = this.props;
const {
contentError,
@@ -596,15 +596,21 @@ class JobOutput extends Component {
{deletionError && (
- this.setState({ deletionError: null })}
- title={i18n._(t`Job Delete Error`)}
- label={i18n._(t`Job Delete Error`)}
- >
-
-
+ <>
+
+ {({ i18n }) => (
+ this.setState({ deletionError: null })}
+ title={i18n._(t`Job Delete Error`)}
+ label={i18n._(t`Job Delete Error`)}
+ >
+
+
+ )}
+
+ >
)}
);
@@ -612,4 +618,4 @@ class JobOutput extends Component {
}
export { JobOutput as _JobOutput };
-export default withI18n()(withRouter(JobOutput));
+export default withRouter(JobOutput);
diff --git a/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx b/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx
index c5b454246f..8c52218c1e 100644
--- a/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx
+++ b/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx
@@ -310,7 +310,17 @@ function ProjectForm({ i18n, project, submitError, ...props }) {
const { summary_fields = {} } = project;
const [contentError, setContentError] = useState(null);
const [isLoading, setIsLoading] = useState(true);
- const [scmSubFormState, setScmSubFormState] = useState(null);
+ const [scmSubFormState, setScmSubFormState] = useState({
+ scm_url: '',
+ scm_branch: '',
+ scm_refspec: '',
+ credential: '',
+ scm_clean: false,
+ scm_delete_on_update: false,
+ scm_update_on_launch: false,
+ allow_override: false,
+ scm_update_cache_timeout: 0,
+ });
const [scmTypeOptions, setScmTypeOptions] = useState(null);
const [credentials, setCredentials] = useState({
scm: { typeId: null, value: null },
diff --git a/awx/ui_next/src/screens/Project/shared/ProjectSyncButton.jsx b/awx/ui_next/src/screens/Project/shared/ProjectSyncButton.jsx
index 864142b046..5a4cc23fdc 100644
--- a/awx/ui_next/src/screens/Project/shared/ProjectSyncButton.jsx
+++ b/awx/ui_next/src/screens/Project/shared/ProjectSyncButton.jsx
@@ -6,8 +6,8 @@ import { SyncIcon } from '@patternfly/react-icons';
import { number } from 'prop-types';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
-
import useRequest, { useDismissableError } from '../../../util/useRequest';
+
import AlertModal from '../../../components/AlertModal';
import ErrorDetail from '../../../components/ErrorDetail';
import { ProjectsAPI } from '../../../api';
diff --git a/awx/ui_next/src/screens/Team/TeamList/TeamListItem.jsx b/awx/ui_next/src/screens/Team/TeamList/TeamListItem.jsx
index 47b2b4011c..6d3691da71 100644
--- a/awx/ui_next/src/screens/Team/TeamList/TeamListItem.jsx
+++ b/awx/ui_next/src/screens/Team/TeamList/TeamListItem.jsx
@@ -27,71 +27,68 @@ const DataListAction = styled(_DataListAction)`
grid-template-columns: 40px;
`;
-class TeamListItem extends React.Component {
- static propTypes = {
+function TeamListItem({ team, isSelected, onSelect, detailUrl, i18n }) {
+ TeamListItem.propTypes = {
team: Team.isRequired,
detailUrl: string.isRequired,
isSelected: bool.isRequired,
onSelect: func.isRequired,
};
- render() {
- const { team, isSelected, onSelect, detailUrl, i18n } = this.props;
- const labelId = `check-action-${team.id}`;
+ const labelId = `check-action-${team.id}`;
- return (
-
-
-
-
-
- {team.name}
-
- ,
-
- {team.summary_fields.organization && (
-
- {i18n._(t`Organization`)} {' '}
-
- {team.summary_fields.organization.name}
-
-
- )}
- ,
- ]}
- />
-
- {team.summary_fields.user_capabilities.edit ? (
-
-
-
-
-
- ) : (
- ''
- )}
-
-
-
- );
- }
+ return (
+
+
+
+
+
+ {team.name}
+
+ ,
+
+ {team.summary_fields.organization && (
+
+ {i18n._(t`Organization`)} {' '}
+
+ {team.summary_fields.organization.name}
+
+
+ )}
+ ,
+ ]}
+ />
+
+ {team.summary_fields.user_capabilities.edit ? (
+
+
+
+
+
+ ) : (
+ ''
+ )}
+
+
+
+ );
}
export default withI18n()(TeamListItem);