mirror of
https://github.com/ansible/awx.git
synced 2026-05-09 02:17:37 -02:30
Fixes bug where user/team role add modal state is not cleared on close
This commit is contained in:
@@ -101,7 +101,9 @@ function SelectResourceStep({
|
|||||||
headerRow={
|
headerRow={
|
||||||
<HeaderRow qsConfig={QS_Config(sortColumns)}>
|
<HeaderRow qsConfig={QS_Config(sortColumns)}>
|
||||||
{sortColumns.map(({ name, key }) => (
|
{sortColumns.map(({ name, key }) => (
|
||||||
<HeaderCell sortKey={key}>{name}</HeaderCell>
|
<HeaderCell sortKey={key} key={key}>
|
||||||
|
{name}
|
||||||
|
</HeaderCell>
|
||||||
))}
|
))}
|
||||||
</HeaderRow>
|
</HeaderRow>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const CheckboxListItem = ({
|
|||||||
|
|
||||||
{columns?.length > 0 ? (
|
{columns?.length > 0 ? (
|
||||||
columns.map(col => (
|
columns.map(col => (
|
||||||
<Td aria-label={col.name} dataLabel={col.key}>
|
<Td aria-label={col.name} dataLabel={col.key} key={col.key}>
|
||||||
{item[col.key]}
|
{item[col.key]}
|
||||||
</Td>
|
</Td>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import React, { useState, useCallback, useEffect, useContext } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
|
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Button, DropdownItem } from '@patternfly/react-core';
|
import useRequest from '../../util/useRequest';
|
||||||
import { KebabifiedContext } from '../../contexts/Kebabified';
|
|
||||||
import useRequest, { useDismissableError } from '../../util/useRequest';
|
|
||||||
import SelectableCard from '../SelectableCard';
|
import SelectableCard from '../SelectableCard';
|
||||||
import AlertModal from '../AlertModal';
|
|
||||||
import ErrorDetail from '../ErrorDetail';
|
|
||||||
import Wizard from '../Wizard/Wizard';
|
import Wizard from '../Wizard/Wizard';
|
||||||
import useSelected from '../../util/useSelected';
|
import useSelected from '../../util/useSelected';
|
||||||
import SelectResourceStep from '../AddRole/SelectResourceStep';
|
import SelectResourceStep from '../AddRole/SelectResourceStep';
|
||||||
@@ -22,21 +17,20 @@ const Grid = styled.div`
|
|||||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function UserAndTeamAccessAdd({ title, onFetchData, apiModel }) {
|
function UserAndTeamAccessAdd({
|
||||||
|
title,
|
||||||
|
onFetchData,
|
||||||
|
apiModel,
|
||||||
|
onClose,
|
||||||
|
onError,
|
||||||
|
}) {
|
||||||
const [selectedResourceType, setSelectedResourceType] = useState(null);
|
const [selectedResourceType, setSelectedResourceType] = useState(null);
|
||||||
const [isWizardOpen, setIsWizardOpen] = useState(false);
|
|
||||||
const [stepIdReached, setStepIdReached] = useState(1);
|
const [stepIdReached, setStepIdReached] = useState(1);
|
||||||
const { id: userId } = useParams();
|
const { id: userId } = useParams();
|
||||||
const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext);
|
|
||||||
const {
|
const {
|
||||||
selected: resourcesSelected,
|
selected: resourcesSelected,
|
||||||
handleSelect: handleResourceSelect,
|
handleSelect: handleResourceSelect,
|
||||||
} = useSelected([]);
|
} = useSelected([]);
|
||||||
useEffect(() => {
|
|
||||||
if (isKebabified) {
|
|
||||||
onKebabModalChange(isWizardOpen);
|
|
||||||
}
|
|
||||||
}, [isKebabified, isWizardOpen, onKebabModalChange]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
selected: rolesSelected,
|
selected: rolesSelected,
|
||||||
@@ -60,13 +54,10 @@ function UserAndTeamAccessAdd({ title, onFetchData, apiModel }) {
|
|||||||
|
|
||||||
await Promise.all(roleRequests);
|
await Promise.all(roleRequests);
|
||||||
onFetchData();
|
onFetchData();
|
||||||
setIsWizardOpen(false);
|
|
||||||
}, [onFetchData, rolesSelected, apiModel, userId, resourcesSelected]),
|
}, [onFetchData, rolesSelected, apiModel, userId, resourcesSelected]),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
const { error, dismissError } = useDismissableError(saveError);
|
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -128,56 +119,22 @@ function UserAndTeamAccessAdd({ title, onFetchData, apiModel }) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (error) {
|
if (saveError) {
|
||||||
return (
|
onError(saveError);
|
||||||
<AlertModal
|
onClose();
|
||||||
aria-label={t`Associate role error`}
|
|
||||||
isOpen={error}
|
|
||||||
variant="error"
|
|
||||||
title={t`Error!`}
|
|
||||||
onClose={dismissError}
|
|
||||||
>
|
|
||||||
{t`Failed to associate role`}
|
|
||||||
<ErrorDetail error={error} />
|
|
||||||
</AlertModal>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Wizard
|
||||||
{isKebabified ? (
|
isOpen
|
||||||
<DropdownItem
|
title={title}
|
||||||
key="add"
|
steps={steps}
|
||||||
component="button"
|
onClose={onClose}
|
||||||
aria-label={t`Add`}
|
onNext={({ id }) =>
|
||||||
onClick={() => setIsWizardOpen(true)}
|
setStepIdReached(stepIdReached < id ? id : stepIdReached)
|
||||||
>
|
}
|
||||||
{t`Add`}
|
onSave={handleWizardSave}
|
||||||
</DropdownItem>
|
/>
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
ouiaId="access-add-button"
|
|
||||||
variant="primary"
|
|
||||||
aria-label={t`Add`}
|
|
||||||
onClick={() => setIsWizardOpen(true)}
|
|
||||||
key="add"
|
|
||||||
>
|
|
||||||
{t`Add`}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{isWizardOpen && (
|
|
||||||
<Wizard
|
|
||||||
isOpen={isWizardOpen}
|
|
||||||
title={title}
|
|
||||||
steps={steps}
|
|
||||||
onClose={() => setIsWizardOpen(false)}
|
|
||||||
onNext={({ id }) =>
|
|
||||||
setStepIdReached(stepIdReached < id ? id : stepIdReached)
|
|
||||||
}
|
|
||||||
onSave={handleWizardSave}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import UserAndTeamAccessAdd from './UserAndTeamAccessAdd';
|
|||||||
|
|
||||||
jest.mock('../../api');
|
jest.mock('../../api');
|
||||||
|
|
||||||
|
const onError = jest.fn();
|
||||||
|
const onClose = jest.fn();
|
||||||
|
|
||||||
describe('<UserAndTeamAccessAdd/>', () => {
|
describe('<UserAndTeamAccessAdd/>', () => {
|
||||||
const resources = {
|
const resources = {
|
||||||
data: {
|
data: {
|
||||||
@@ -57,24 +60,21 @@ describe('<UserAndTeamAccessAdd/>', () => {
|
|||||||
<UserAndTeamAccessAdd
|
<UserAndTeamAccessAdd
|
||||||
apiModel={UsersAPI}
|
apiModel={UsersAPI}
|
||||||
onFetchData={() => {}}
|
onFetchData={() => {}}
|
||||||
|
onClose={onClose}
|
||||||
title="Add user permissions"
|
title="Add user permissions"
|
||||||
|
onError={onError}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
await waitForElement(wrapper, 'Button[aria-label="Add"]');
|
wrapper.update();
|
||||||
});
|
});
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
});
|
});
|
||||||
test('should mount properly', async () => {
|
test('should mount properly', async () => {
|
||||||
expect(wrapper.find('Button[aria-label="Add"]').length).toBe(1);
|
|
||||||
act(() => wrapper.find('Button[aria-label="Add"]').prop('onClick')());
|
|
||||||
wrapper.update();
|
|
||||||
expect(wrapper.find('PFWizard').length).toBe(1);
|
expect(wrapper.find('PFWizard').length).toBe(1);
|
||||||
});
|
});
|
||||||
test('should disable steps', async () => {
|
test('should disable steps', async () => {
|
||||||
act(() => wrapper.find('Button[aria-label="Add"]').prop('onClick')());
|
|
||||||
wrapper.update();
|
|
||||||
expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe(true);
|
expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe(true);
|
||||||
expect(
|
expect(
|
||||||
wrapper
|
wrapper
|
||||||
@@ -122,8 +122,6 @@ describe('<UserAndTeamAccessAdd/>', () => {
|
|||||||
JobTemplatesAPI.read.mockResolvedValue(resources);
|
JobTemplatesAPI.read.mockResolvedValue(resources);
|
||||||
JobTemplatesAPI.readOptions.mockResolvedValue(options);
|
JobTemplatesAPI.readOptions.mockResolvedValue(options);
|
||||||
UsersAPI.associateRole.mockResolvedValue({});
|
UsersAPI.associateRole.mockResolvedValue({});
|
||||||
act(() => wrapper.find('Button[aria-label="Add"]').prop('onClick')());
|
|
||||||
wrapper.update();
|
|
||||||
await act(async () =>
|
await act(async () =>
|
||||||
wrapper.find('SelectableCard[label="Job templates"]').prop('onClick')({
|
wrapper.find('SelectableCard[label="Job templates"]').prop('onClick')({
|
||||||
fetchItems: JobTemplatesAPI.read,
|
fetchItems: JobTemplatesAPI.read,
|
||||||
@@ -179,15 +177,16 @@ describe('<UserAndTeamAccessAdd/>', () => {
|
|||||||
await expect(UsersAPI.associateRole).toHaveBeenCalled();
|
await expect(UsersAPI.associateRole).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should close wizard', async () => {
|
test('should close wizard on cancel', async () => {
|
||||||
act(() => wrapper.find('Button[aria-label="Add"]').prop('onClick')());
|
await act(async () =>
|
||||||
|
wrapper.find('Button[children="Cancel"]').prop('onClick')()
|
||||||
|
);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
act(() => wrapper.find('PFWizard').prop('onClose')());
|
expect(onClose).toHaveBeenCalledTimes(1);
|
||||||
wrapper.update();
|
|
||||||
expect(wrapper.find('PFWizard').length).toBe(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should throw error', async () => {
|
test('should throw error', async () => {
|
||||||
|
expect(onError).toHaveBeenCalledTimes(0);
|
||||||
JobTemplatesAPI.read.mockResolvedValue(resources);
|
JobTemplatesAPI.read.mockResolvedValue(resources);
|
||||||
JobTemplatesAPI.readOptions.mockResolvedValue(options);
|
JobTemplatesAPI.readOptions.mockResolvedValue(options);
|
||||||
UsersAPI.associateRole.mockRejectedValue(
|
UsersAPI.associateRole.mockRejectedValue(
|
||||||
@@ -210,9 +209,6 @@ describe('<UserAndTeamAccessAdd/>', () => {
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
act(() => wrapper.find('Button[aria-label="Add"]').prop('onClick')());
|
|
||||||
wrapper.update();
|
|
||||||
|
|
||||||
await act(async () =>
|
await act(async () =>
|
||||||
wrapper.find('SelectableCard[label="Job templates"]').prop('onClick')({
|
wrapper.find('SelectableCard[label="Job templates"]').prop('onClick')({
|
||||||
fetchItems: JobTemplatesAPI.read,
|
fetchItems: JobTemplatesAPI.read,
|
||||||
@@ -261,6 +257,6 @@ describe('<UserAndTeamAccessAdd/>', () => {
|
|||||||
|
|
||||||
await expect(UsersAPI.associateRole).toHaveBeenCalled();
|
await expect(UsersAPI.associateRole).toHaveBeenCalled();
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(wrapper.find('AlertModal').length).toBe(1);
|
expect(onError).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
@@ -22,6 +20,7 @@ import { getQSConfig, parseQueryString } from '../../../util/qs';
|
|||||||
import ErrorDetail from '../../../components/ErrorDetail';
|
import ErrorDetail from '../../../components/ErrorDetail';
|
||||||
import AlertModal from '../../../components/AlertModal';
|
import AlertModal from '../../../components/AlertModal';
|
||||||
import TeamRoleListItem from './TeamRoleListItem';
|
import TeamRoleListItem from './TeamRoleListItem';
|
||||||
|
import { ToolbarAddButton } from '../../../components/PaginatedDataList';
|
||||||
import UserAndTeamAccessAdd from '../../../components/UserAndTeamAccessAdd/UserAndTeamAccessAdd';
|
import UserAndTeamAccessAdd from '../../../components/UserAndTeamAccessAdd/UserAndTeamAccessAdd';
|
||||||
|
|
||||||
const QS_CONFIG = getQSConfig('roles', {
|
const QS_CONFIG = getQSConfig('roles', {
|
||||||
@@ -33,6 +32,8 @@ const QS_CONFIG = getQSConfig('roles', {
|
|||||||
function TeamRolesList({ me, team }) {
|
function TeamRolesList({ me, team }) {
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const [roleToDisassociate, setRoleToDisassociate] = useState(null);
|
const [roleToDisassociate, setRoleToDisassociate] = useState(null);
|
||||||
|
const [showAddModal, setShowAddModal] = useState(false);
|
||||||
|
const [associateError, setAssociateError] = useState(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -165,10 +166,10 @@ function TeamRolesList({ me, team }) {
|
|||||||
additionalControls={[
|
additionalControls={[
|
||||||
...(canAdd
|
...(canAdd
|
||||||
? [
|
? [
|
||||||
<UserAndTeamAccessAdd
|
<ToolbarAddButton
|
||||||
apiModel={TeamsAPI}
|
ouiaId="role-add-button"
|
||||||
onFetchData={fetchRoles}
|
key="add"
|
||||||
title={t`Add team permissions`}
|
onClick={() => setShowAddModal(true)}
|
||||||
/>,
|
/>,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
@@ -192,7 +193,18 @@ function TeamRolesList({ me, team }) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{showAddModal && (
|
||||||
|
<UserAndTeamAccessAdd
|
||||||
|
apiModel={TeamsAPI}
|
||||||
|
onFetchData={() => {
|
||||||
|
setShowAddModal(false);
|
||||||
|
fetchRoles();
|
||||||
|
}}
|
||||||
|
title={t`Add team permissions`}
|
||||||
|
onClose={() => setShowAddModal(false)}
|
||||||
|
onError={err => setAssociateError(err)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{roleToDisassociate && (
|
{roleToDisassociate && (
|
||||||
<AlertModal
|
<AlertModal
|
||||||
aria-label={t`Disassociate role`}
|
aria-label={t`Disassociate role`}
|
||||||
@@ -228,6 +240,18 @@ function TeamRolesList({ me, team }) {
|
|||||||
</div>
|
</div>
|
||||||
</AlertModal>
|
</AlertModal>
|
||||||
)}
|
)}
|
||||||
|
{associateError && (
|
||||||
|
<AlertModal
|
||||||
|
aria-label={t`Associate role error`}
|
||||||
|
isOpen={associateError}
|
||||||
|
variant="error"
|
||||||
|
title={t`Error!`}
|
||||||
|
onClose={() => setAssociateError(null)}
|
||||||
|
>
|
||||||
|
{t`Failed to associate role`}
|
||||||
|
<ErrorDetail error={associateError} />
|
||||||
|
</AlertModal>
|
||||||
|
)}
|
||||||
{disassociationError && (
|
{disassociationError && (
|
||||||
<AlertModal
|
<AlertModal
|
||||||
isOpen={disassociationError}
|
isOpen={disassociationError}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import PaginatedTable, {
|
|||||||
} from '../../../components/PaginatedTable';
|
} from '../../../components/PaginatedTable';
|
||||||
import ErrorDetail from '../../../components/ErrorDetail';
|
import ErrorDetail from '../../../components/ErrorDetail';
|
||||||
import AlertModal from '../../../components/AlertModal';
|
import AlertModal from '../../../components/AlertModal';
|
||||||
|
import { ToolbarAddButton } from '../../../components/PaginatedDataList';
|
||||||
import DatalistToolbar from '../../../components/DataListToolbar';
|
import DatalistToolbar from '../../../components/DataListToolbar';
|
||||||
import UserRolesListItem from './UserRolesListItem';
|
import UserRolesListItem from './UserRolesListItem';
|
||||||
import UserAndTeamAccessAdd from '../../../components/UserAndTeamAccessAdd/UserAndTeamAccessAdd';
|
import UserAndTeamAccessAdd from '../../../components/UserAndTeamAccessAdd/UserAndTeamAccessAdd';
|
||||||
@@ -34,6 +34,8 @@ const QS_CONFIG = getQSConfig('roles', {
|
|||||||
function UserRolesList({ user }) {
|
function UserRolesList({ user }) {
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const [roleToDisassociate, setRoleToDisassociate] = useState(null);
|
const [roleToDisassociate, setRoleToDisassociate] = useState(null);
|
||||||
|
const [showAddModal, setShowAddModal] = useState(false);
|
||||||
|
const [associateError, setAssociateError] = useState(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -178,10 +180,10 @@ function UserRolesList({ user }) {
|
|||||||
additionalControls={[
|
additionalControls={[
|
||||||
...(canAdd
|
...(canAdd
|
||||||
? [
|
? [
|
||||||
<UserAndTeamAccessAdd
|
<ToolbarAddButton
|
||||||
apiModel={UsersAPI}
|
ouiaId="role-add-button"
|
||||||
onFetchData={fetchRoles}
|
key="add"
|
||||||
title={t`Add user permissions`}
|
onClick={() => setShowAddModal(true)}
|
||||||
/>,
|
/>,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
@@ -189,6 +191,18 @@ function UserRolesList({ user }) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{showAddModal && (
|
||||||
|
<UserAndTeamAccessAdd
|
||||||
|
apiModel={UsersAPI}
|
||||||
|
onFetchData={() => {
|
||||||
|
setShowAddModal(false);
|
||||||
|
fetchRoles();
|
||||||
|
}}
|
||||||
|
title={t`Add user permissions`}
|
||||||
|
onClose={() => setShowAddModal(false)}
|
||||||
|
onError={err => setAssociateError(err)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{roleToDisassociate && (
|
{roleToDisassociate && (
|
||||||
<AlertModal
|
<AlertModal
|
||||||
aria-label={t`Disassociate role`}
|
aria-label={t`Disassociate role`}
|
||||||
@@ -224,6 +238,18 @@ function UserRolesList({ user }) {
|
|||||||
</div>
|
</div>
|
||||||
</AlertModal>
|
</AlertModal>
|
||||||
)}
|
)}
|
||||||
|
{associateError && (
|
||||||
|
<AlertModal
|
||||||
|
aria-label={t`Associate role error`}
|
||||||
|
isOpen={associateError}
|
||||||
|
variant="error"
|
||||||
|
title={t`Error!`}
|
||||||
|
onClose={() => setAssociateError(null)}
|
||||||
|
>
|
||||||
|
{t`Failed to associate role`}
|
||||||
|
<ErrorDetail error={associateError} />
|
||||||
|
</AlertModal>
|
||||||
|
)}
|
||||||
{disassociationError && (
|
{disassociationError && (
|
||||||
<AlertModal
|
<AlertModal
|
||||||
isOpen={disassociationError}
|
isOpen={disassociationError}
|
||||||
|
|||||||
Reference in New Issue
Block a user