mirror of
https://github.com/ansible/awx.git
synced 2026-03-18 17:37:30 -02:30
Merge pull request #9563 from AlexSCorey/8769-WizardFailure
Fixes crashing wizard, and adds error handle on adding role SUMMARY This addresses #8769. It also adds error handling if there is some sort of request error during the submit request. ISSUE TYPE Bugfix Pull Request COMPONENT NAME UI AWX VERSION ADDITIONAL INFORMATION Reviewed-by: John Mitchell <None>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import React, { Fragment, useState } from 'react';
|
import React, { Fragment, useState, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import SelectableCard from '../SelectableCard';
|
import SelectableCard from '../SelectableCard';
|
||||||
@@ -17,7 +18,9 @@ const readTeams = async queryParams => TeamsAPI.read(queryParams);
|
|||||||
|
|
||||||
const readTeamsOptions = async () => TeamsAPI.readOptions();
|
const readTeamsOptions = async () => TeamsAPI.readOptions();
|
||||||
|
|
||||||
function AddResourceRole({ onSave, onClose, roles, i18n, resource }) {
|
function AddResourceRole({ onSave, onClose, roles, i18n, resource, onError }) {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
const [selectedResource, setSelectedResource] = useState(null);
|
const [selectedResource, setSelectedResource] = useState(null);
|
||||||
const [selectedResourceRows, setSelectedResourceRows] = useState([]);
|
const [selectedResourceRows, setSelectedResourceRows] = useState([]);
|
||||||
const [selectedRoleRows, setSelectedRoleRows] = useState([]);
|
const [selectedRoleRows, setSelectedRoleRows] = useState([]);
|
||||||
@@ -39,6 +42,12 @@ function AddResourceRole({ onSave, onClose, roles, i18n, resource }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentStepId === 1 && maxEnabledStep > 1) {
|
||||||
|
history.push(history.location.pathname);
|
||||||
|
}
|
||||||
|
}, [currentStepId, history, maxEnabledStep]);
|
||||||
|
|
||||||
const handleRoleCheckboxClick = role => {
|
const handleRoleCheckboxClick = role => {
|
||||||
const selectedIndex = selectedRoleRows.findIndex(
|
const selectedIndex = selectedRoleRows.findIndex(
|
||||||
selectedRow => selectedRow.id === role.id
|
selectedRow => selectedRow.id === role.id
|
||||||
@@ -94,7 +103,8 @@ function AddResourceRole({ onSave, onClose, roles, i18n, resource }) {
|
|||||||
await Promise.all(roleRequests);
|
await Promise.all(roleRequests);
|
||||||
onSave();
|
onSave();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// TODO: handle this error
|
onError(err);
|
||||||
|
onClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable react/jsx-pascal-case */
|
/* eslint-disable react/jsx-pascal-case */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -13,6 +14,11 @@ import { TeamsAPI, UsersAPI } from '../../api';
|
|||||||
jest.mock('../../api/models/Teams');
|
jest.mock('../../api/models/Teams');
|
||||||
jest.mock('../../api/models/Users');
|
jest.mock('../../api/models/Users');
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
...jest.requireActual('react-router-dom'),
|
||||||
|
|
||||||
|
useHistory: () => ({ push: jest.fn(), location: { pathname: {} } }),
|
||||||
|
}));
|
||||||
// TODO: Once error handling is functional in
|
// TODO: Once error handling is functional in
|
||||||
// this component write tests for it
|
// this component write tests for it
|
||||||
|
|
||||||
@@ -111,6 +117,112 @@ describe('<_AddResourceRole />', () => {
|
|||||||
expect(UsersAPI.associateRole).toBeCalledWith(1, 1);
|
expect(UsersAPI.associateRole).toBeCalledWith(1, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should call on error properly', async () => {
|
||||||
|
let wrapper;
|
||||||
|
const onError = jest.fn();
|
||||||
|
UsersAPI.associateRole.mockRejectedValue(
|
||||||
|
new Error({
|
||||||
|
response: {
|
||||||
|
config: {
|
||||||
|
method: 'post',
|
||||||
|
url: '/api/v2/users',
|
||||||
|
},
|
||||||
|
data: 'An error occurred',
|
||||||
|
status: 403,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
act(() => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<AddResourceRole
|
||||||
|
onClose={() => {}}
|
||||||
|
onError={onError}
|
||||||
|
onSave={() => {}}
|
||||||
|
roles={roles}
|
||||||
|
/>,
|
||||||
|
{ context: { network: { handleHttpError: () => {} } } }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save
|
||||||
|
await act(async () =>
|
||||||
|
wrapper.find('Button[type="submit"]').prop('onClick')()
|
||||||
|
);
|
||||||
|
expect(UsersAPI.associateRole).toBeCalledWith(1, 1);
|
||||||
|
expect(onError).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should should update history properly', async () => {
|
||||||
|
let wrapper;
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['organizations/2/access?resource.order_by=-username'],
|
||||||
|
});
|
||||||
|
act(() => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<AddResourceRole onClose={() => {}} onSave={() => {}} roles={roles} />,
|
||||||
|
{ context: { router: { history } } }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
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
|
||||||
|
);
|
||||||
|
await act(async () =>
|
||||||
|
wrapper.find('PFWizard').prop('onGoToStep')({ id: 1 })
|
||||||
|
);
|
||||||
|
wrapper.update();
|
||||||
|
expect(history.location.pathname).toEqual('organizations/2/access');
|
||||||
|
});
|
||||||
|
|
||||||
test('should successfuly click user/team cards', async () => {
|
test('should successfuly click user/team cards', async () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
act(() => {
|
act(() => {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { getQSConfig, parseQueryString } from '../../util/qs';
|
|||||||
import useRequest, { useDeleteItems } from '../../util/useRequest';
|
import useRequest, { useDeleteItems } from '../../util/useRequest';
|
||||||
import DeleteRoleConfirmationModal from './DeleteRoleConfirmationModal';
|
import DeleteRoleConfirmationModal from './DeleteRoleConfirmationModal';
|
||||||
import ResourceAccessListItem from './ResourceAccessListItem';
|
import ResourceAccessListItem from './ResourceAccessListItem';
|
||||||
|
import ErrorDetail from '../ErrorDetail';
|
||||||
|
|
||||||
const QS_CONFIG = getQSConfig('access', {
|
const QS_CONFIG = getQSConfig('access', {
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -19,6 +20,7 @@ const QS_CONFIG = getQSConfig('access', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function ResourceAccessList({ i18n, apiModel, resource }) {
|
function ResourceAccessList({ i18n, apiModel, resource }) {
|
||||||
|
const [submitError, setSubmitError] = useState(null);
|
||||||
const [deletionRecord, setDeletionRecord] = useState(null);
|
const [deletionRecord, setDeletionRecord] = useState(null);
|
||||||
const [deletionRole, setDeletionRole] = useState(null);
|
const [deletionRole, setDeletionRole] = useState(null);
|
||||||
const [showAddModal, setShowAddModal] = useState(false);
|
const [showAddModal, setShowAddModal] = useState(false);
|
||||||
@@ -206,6 +208,7 @@ function ResourceAccessList({ i18n, apiModel, resource }) {
|
|||||||
setShowAddModal(false);
|
setShowAddModal(false);
|
||||||
fetchAccessRecords();
|
fetchAccessRecords();
|
||||||
}}
|
}}
|
||||||
|
onError={err => setSubmitError(err)}
|
||||||
roles={resource.summary_fields.object_roles}
|
roles={resource.summary_fields.object_roles}
|
||||||
resource={resource}
|
resource={resource}
|
||||||
/>
|
/>
|
||||||
@@ -227,6 +230,17 @@ function ResourceAccessList({ i18n, apiModel, resource }) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{submitError && (
|
||||||
|
<AlertModal
|
||||||
|
variant="error"
|
||||||
|
title={i18n._(t`Error!`)}
|
||||||
|
isOpen={submitError}
|
||||||
|
onClose={() => setSubmitError(null)}
|
||||||
|
>
|
||||||
|
{i18n._(t`Failed to assign roles properly`)}
|
||||||
|
<ErrorDetail error={submitError} />
|
||||||
|
</AlertModal>
|
||||||
|
)}
|
||||||
{deletionError && (
|
{deletionError && (
|
||||||
<AlertModal
|
<AlertModal
|
||||||
isOpen={deletionError}
|
isOpen={deletionError}
|
||||||
|
|||||||
Reference in New Issue
Block a user