diff --git a/src/components/Lookup/Lookup.jsx b/src/components/Lookup/Lookup.jsx index c65eee0935..ec189fb1b2 100644 --- a/src/components/Lookup/Lookup.jsx +++ b/src/components/Lookup/Lookup.jsx @@ -15,6 +15,8 @@ import { import { I18n } from '@lingui/react'; import { Trans, t } from '@lingui/macro'; +import { withNetwork } from '../../contexts/Network'; + import CheckboxListItem from '../ListItem'; import DataListToolbar from '../DataListToolbar'; import SelectedList from '../SelectedList'; @@ -67,7 +69,7 @@ class Lookup extends React.Component { } async getData () { - const { getItems } = this.props; + const { getItems, handleHttpError } = this.props; const { page, page_size, sortedColumnKey, sortOrder } = this.state; this.setState({ error: false }); @@ -92,7 +94,7 @@ class Lookup extends React.Component { this.setState(stateToUpdate); } catch (err) { - this.setState({ error: true }); + handleHttpError(err) || this.setState({ error: true }); } } @@ -273,4 +275,4 @@ Lookup.defaultProps = { name: null, }; -export default Lookup; +export default withNetwork(Lookup); diff --git a/src/components/NotificationsList/Notifications.list.jsx b/src/components/NotificationsList/Notifications.list.jsx index 80ed1c2407..c3c44381ca 100644 --- a/src/components/NotificationsList/Notifications.list.jsx +++ b/src/components/NotificationsList/Notifications.list.jsx @@ -8,6 +8,8 @@ import { CubesIcon } from '@patternfly/react-icons'; import { I18n, i18nMark } from '@lingui/react'; import { Trans, t } from '@lingui/macro'; +import { withNetwork } from '../../contexts/Network'; + import DataListToolbar from '../DataListToolbar'; import NotificationListItem from './NotificationListItem'; import Pagination from '../Pagination'; @@ -117,58 +119,71 @@ class Notifications extends Component { } async createError (id, isCurrentlyOn) { - const { onCreateError, match } = this.props; + const { onCreateError, match, handleHttpError } = this.props; const postParams = { id }; + let errorHandled; if (isCurrentlyOn) { postParams.disassociate = true; } try { await onCreateError(match.params.id, postParams); } catch (err) { - this.setState({ error: true }); + errorHandled = handleHttpError(err); + if (!errorHandled) { + this.setState({ error: true }); + } } finally { - if (isCurrentlyOn) { - // Remove it from state - this.setState((prevState) => ({ - errorTemplateIds: prevState.errorTemplateIds.filter((templateId) => templateId !== id) - })); - } else { - // Add it to state - this.setState(prevState => ({ - errorTemplateIds: [...prevState.errorTemplateIds, id] - })); + if (!errorHandled) { + if (isCurrentlyOn) { + // Remove it from state + this.setState((prevState) => ({ + errorTemplateIds: prevState.errorTemplateIds.filter((templateId) => templateId !== id) + })); + } else { + // Add it to state + this.setState(prevState => ({ + errorTemplateIds: [...prevState.errorTemplateIds, id] + })); + } } } } async createSuccess (id, isCurrentlyOn) { - const { onCreateSuccess, match } = this.props; + const { onCreateSuccess, match, handleHttpError } = this.props; const postParams = { id }; + let errorHandled; if (isCurrentlyOn) { postParams.disassociate = true; } try { await onCreateSuccess(match.params.id, postParams); } catch (err) { - this.setState({ error: true }); + errorHandled = handleHttpError(err); + if (!errorHandled) { + this.setState({ error: true }); + } } finally { - if (isCurrentlyOn) { - // Remove it from state - this.setState((prevState) => ({ - successTemplateIds: prevState.successTemplateIds.filter((templateId) => templateId !== id) - })); - } else { - // Add it to state - this.setState(prevState => ({ - successTemplateIds: [...prevState.successTemplateIds, id] - })); + if (!errorHandled) { + if (isCurrentlyOn) { + // Remove it from state + this.setState((prevState) => ({ + successTemplateIds: prevState.successTemplateIds + .filter((templateId) => templateId !== id) + })); + } else { + // Add it to state + this.setState(prevState => ({ + successTemplateIds: [...prevState.successTemplateIds, id] + })); + } } } } async readNotifications (queryParams) { const { noInitialResults } = this.state; - const { onReadNotifications, onReadSuccess, onReadError, match } = this.props; + const { onReadNotifications, onReadSuccess, onReadError, match, handleHttpError } = this.props; const { page, page_size, order_by } = queryParams; let sortOrder = 'ascending'; @@ -233,12 +248,11 @@ class Notifications extends Component { } this.setState({ successTemplateIds, - errorTemplateIds + errorTemplateIds, + loading: false }); } catch (err) { - this.setState({ error: true }); - } finally { - this.setState({ loading: false }); + handleHttpError(err) || this.setState({ error: true, loading: false }); } } @@ -329,4 +343,4 @@ Notifications.propTypes = { onCreateSuccess: PropTypes.func.isRequired, }; -export default Notifications; +export default withNetwork(Notifications); diff --git a/src/pages/Organizations/Organizations.jsx b/src/pages/Organizations/Organizations.jsx index 7b5d41377e..28ac5864ab 100644 --- a/src/pages/Organizations/Organizations.jsx +++ b/src/pages/Organizations/Organizations.jsx @@ -1,11 +1,15 @@ import React, { Component, Fragment } from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Route, withRouter, Switch } from 'react-router-dom'; import { i18nMark } from '@lingui/react'; +import { NetworkProvider } from '../../contexts/Network'; +import { withRootDialog } from '../../contexts/RootDialog'; + +import Breadcrumbs from '../../components/Breadcrumbs/Breadcrumbs'; + import OrganizationsList from './screens/OrganizationsList'; import OrganizationAdd from './screens/OrganizationAdd'; import Organization from './screens/Organization/Organization'; -import Breadcrumbs from '../../components/Breadcrumbs/Breadcrumbs'; class Organizations extends Component { state = { @@ -13,7 +17,7 @@ class Organizations extends Component { '/organizations': i18nMark('Organizations'), '/organizations/add': i18nMark('Create New Organization') } - } + }; setBreadcrumbConfig = (organization) => { if (!organization) { @@ -35,7 +39,7 @@ class Organizations extends Component { } render () { - const { match, api, history, location } = this.props; + const { match, history, location, setRootDialogMessage } = this.props; const { breadcrumbConfig } = this.state; return ( @@ -47,28 +51,34 @@ class Organizations extends Component { ( - + )} /> ( - + render={({ match: newRouteMatch }) => ( + { + history.replace('/organizations'); + setRootDialogMessage({ + title: '404', + bodyText: `Cannot find organization with ID ${newRouteMatch.params.id}.`, + variant: 'warning' + }); + }} + > + + )} /> ( - + )} /> @@ -77,4 +87,4 @@ class Organizations extends Component { } } -export default Organizations; +export default withRootDialog(withRouter(Organizations)); diff --git a/src/pages/Organizations/components/InstanceGroupsLookup.jsx b/src/pages/Organizations/components/InstanceGroupsLookup.jsx index ac48d25972..703fd2420c 100644 --- a/src/pages/Organizations/components/InstanceGroupsLookup.jsx +++ b/src/pages/Organizations/components/InstanceGroupsLookup.jsx @@ -7,6 +7,8 @@ import { t } from '@lingui/macro'; import Lookup from '../../../components/Lookup'; +import { withNetwork } from '../../../contexts/Network'; + const INSTANCE_GROUPS_LOOKUP_COLUMNS = [ { name: i18nMark('Name'), key: 'name', isSortable: true }, { name: i18nMark('Modified'), key: 'modified', isSortable: false, isNumeric: true }, @@ -69,9 +71,6 @@ class InstanceGroupsLookup extends React.Component { } InstanceGroupsLookup.propTypes = { - api: PropTypes.shape({ - getInstanceGroups: PropTypes.func, - }).isRequired, value: PropTypes.arrayOf(PropTypes.object).isRequired, tooltip: PropTypes.string, onChange: PropTypes.func.isRequired, @@ -81,4 +80,4 @@ InstanceGroupsLookup.defaultProps = { tooltip: '', }; -export default InstanceGroupsLookup; +export default withNetwork(InstanceGroupsLookup); diff --git a/src/pages/Organizations/components/OrganizationAccessList.jsx b/src/pages/Organizations/components/OrganizationAccessList.jsx index 7e31033025..983bba1e01 100644 --- a/src/pages/Organizations/components/OrganizationAccessList.jsx +++ b/src/pages/Organizations/components/OrganizationAccessList.jsx @@ -13,6 +13,8 @@ import { Link } from 'react-router-dom'; +import { withNetwork } from '../../../contexts/Network'; + import AlertModal from '../../../components/AlertModal'; import Pagination from '../../../components/Pagination'; import DataListToolbar from '../../../components/DataListToolbar'; @@ -238,20 +240,16 @@ class OrganizationAccessList extends React.Component { } async removeAccessRole (roleId, resourceId, type) { - const { removeRole } = this.props; + const { removeRole, handleHttpError } = this.props; const url = `/api/v2/${type}/${resourceId}/roles/`; try { await removeRole(url, roleId); + const queryParams = this.getQueryParams(); + await this.fetchOrgAccessList(queryParams); + this.setState({ showWarning: false }); } catch (error) { - this.setState({ error }); + handleHttpError(error) || this.setState({ error }); } - const queryParams = this.getQueryParams(); - try { - this.fetchOrgAccessList(queryParams); - } catch (error) { - this.setState({ error }); - } - this.setState({ showWarning: false }); } handleWarning (roleName, roleId, resourceName, resourceId, type) { @@ -432,4 +430,4 @@ OrganizationAccessList.propTypes = { removeRole: PropTypes.func.isRequired, }; -export default OrganizationAccessList; +export default withNetwork(OrganizationAccessList); diff --git a/src/pages/Organizations/components/OrganizationForm.jsx b/src/pages/Organizations/components/OrganizationForm.jsx index 4000bf1a1c..37994f1bd6 100644 --- a/src/pages/Organizations/components/OrganizationForm.jsx +++ b/src/pages/Organizations/components/OrganizationForm.jsx @@ -9,7 +9,8 @@ import { FormGroup, } from '@patternfly/react-core'; -import { ConfigContext } from '../../../context'; +import { Config } from '../../../contexts/Config'; +import { withNetwork } from '../../../contexts/Network'; import FormRow from '../../../components/FormRow'; import FormField from '../../../components/FormField'; import FormActionGroup from '../../../components/FormActionGroup'; @@ -81,7 +82,7 @@ class OrganizationForm extends Component { } render () { - const { api, organization, handleCancel } = this.props; + const { organization, handleCancel } = this.props; const { instanceGroups, formIsValid, error } = this.state; const defaultVenv = '/venv/ansible/'; @@ -112,7 +113,7 @@ class OrganizationForm extends Component { type="text" label={i18n._(t`Description`)} /> - + {({ custom_virtualenvs }) => ( custom_virtualenvs && custom_virtualenvs.length > 1 && ( ) )} - + ( @@ -137,7 +138,6 @@ class Organization extends Component { path="/organizations/:id/details" render={() => ( @@ -148,7 +148,6 @@ class Organization extends Component { path="/organizations/:id/access" render={() => ( ( )} /> @@ -168,7 +169,6 @@ class Organization extends Component { path="/organizations/:id/notifications" render={() => ( { await api.associateInstanceGroup(instanceGroupsUrl, id); })); - } catch (err) { - this.setState({ error: err }); - } finally { this.handleSuccess(response.id); + } catch (err) { + handleHttpError(err) || this.setState({ error: err }); } } catch (err) { this.setState({ error: err }); @@ -58,7 +59,6 @@ class OrganizationAdd extends React.Component { } render () { - const { api } = this.props; const { error } = this.state; return ( @@ -82,7 +82,6 @@ class OrganizationAdd extends React.Component { @@ -105,4 +104,4 @@ OrganizationAdd.contextTypes = { }; export { OrganizationAdd as _OrganizationAdd }; -export default withRouter(OrganizationAdd); +export default withNetwork(withRouter(OrganizationAdd)); diff --git a/src/pages/Organizations/screens/OrganizationsList.jsx b/src/pages/Organizations/screens/OrganizationsList.jsx index 2f50887c79..e1bfe618b4 100644 --- a/src/pages/Organizations/screens/OrganizationsList.jsx +++ b/src/pages/Organizations/screens/OrganizationsList.jsx @@ -21,6 +21,9 @@ import { } from '@patternfly/react-core'; import { CubesIcon } from '@patternfly/react-icons'; + +import { withNetwork } from '../../../contexts/Network'; + import DataListToolbar from '../../../components/DataListToolbar'; import OrganizationListItem from '../components/OrganizationListItem'; import Pagination from '../../../components/Pagination'; @@ -170,14 +173,21 @@ class OrganizationsList extends Component { } async handleOrgDelete (event) { - const { orgsToDelete } = this.state; + const { orgsToDelete, handleHttpError } = this.state; const { api } = this.props; + let errorHandled; - orgsToDelete.forEach(async (org) => { - await api.destroyOrganization(org.id); + try { + await Promise.all(orgsToDelete.map(async (org) => api.destroyOrganization(org.id))); + this.handleClearOrgsToDelete(); + } catch (err) { + errorHandled = handleHttpError(err); + } finally { + if (!errorHandled) { const queryParams = this.getQueryParams(); this.fetchOrganizations(queryParams); - }); + } + } event.preventDefault(); } @@ -192,7 +202,7 @@ class OrganizationsList extends Component { } async fetchOrganizations (queryParams) { - const { api } = this.props; + const { api, handleHttpError } = this.props; const { page, page_size, order_by } = queryParams; let sortOrder = 'ascending'; @@ -220,6 +230,7 @@ class OrganizationsList extends Component { sortedColumnKey, results, selected: [], + loading: false }; // This is in place to track whether or not the initial request @@ -233,9 +244,7 @@ class OrganizationsList extends Component { this.setState(stateToUpdate); this.updateUrl(queryParams); } catch (err) { - this.setState({ error: true }); - } finally { - this.setState({ loading: false }); + handleHttpError(err) || this.setState({ error: true, loading: false }); } } @@ -271,8 +280,8 @@ class OrganizationsList extends Component { isOpen={isModalOpen} onClose={this.handleClearOrgsToDelete} actions={[ - , - + , + ]} > {warningMsg} @@ -350,4 +359,4 @@ class OrganizationsList extends Component { } } -export default withRouter(OrganizationsList); +export default withNetwork(withRouter(OrganizationsList));