mirror of
https://github.com/ZwareBear/awx.git
synced 2026-04-21 15:31:48 -05:00
update content loading and error handling
unwind error handling use auth cookie as source of truth, fetch config only when authenticated
This commit is contained in:
@@ -4,9 +4,6 @@ import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import { Config } from '../../contexts/Config';
|
||||
import { NetworkProvider } from '../../contexts/Network';
|
||||
import { withRootDialog } from '../../contexts/RootDialog';
|
||||
|
||||
import Breadcrumbs from '../../components/Breadcrumbs/Breadcrumbs';
|
||||
|
||||
import OrganizationsList from './screens/OrganizationsList';
|
||||
@@ -49,7 +46,7 @@ class Organizations extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { match, history, location, setRootDialogMessage, i18n } = this.props;
|
||||
const { match, history, location } = this.props;
|
||||
const { breadcrumbConfig } = this.state;
|
||||
|
||||
return (
|
||||
@@ -66,34 +63,17 @@ class Organizations extends Component {
|
||||
/>
|
||||
<Route
|
||||
path={`${match.path}/:id`}
|
||||
render={({ match: newRouteMatch }) => (
|
||||
<NetworkProvider
|
||||
handle404={() => {
|
||||
history.replace('/organizations');
|
||||
setRootDialogMessage({
|
||||
title: '404',
|
||||
bodyText: (
|
||||
<Fragment>
|
||||
{i18n._(t`Cannot find organization with ID`)}
|
||||
<strong>{` ${newRouteMatch.params.id}`}</strong>
|
||||
.
|
||||
</Fragment>
|
||||
),
|
||||
variant: 'warning'
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Config>
|
||||
{({ me }) => (
|
||||
<Organization
|
||||
history={history}
|
||||
location={location}
|
||||
setBreadcrumb={this.setBreadcrumbConfig}
|
||||
me={me || {}}
|
||||
/>
|
||||
)}
|
||||
</Config>
|
||||
</NetworkProvider>
|
||||
render={() => (
|
||||
<Config>
|
||||
{({ me }) => (
|
||||
<Organization
|
||||
history={history}
|
||||
location={location}
|
||||
setBreadcrumb={this.setBreadcrumbConfig}
|
||||
me={me || {}}
|
||||
/>
|
||||
)}
|
||||
</Config>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
@@ -109,4 +89,4 @@ class Organizations extends Component {
|
||||
}
|
||||
|
||||
export { Organizations as _Organizations };
|
||||
export default withI18n()(withRootDialog(withRouter(Organizations)));
|
||||
export default withI18n()(withRouter(Organizations));
|
||||
|
||||
@@ -7,8 +7,6 @@ import { QuestionCircleIcon } from '@patternfly/react-icons';
|
||||
|
||||
import Lookup from '../../../components/Lookup';
|
||||
|
||||
import { withNetwork } from '../../../contexts/Network';
|
||||
|
||||
import { InstanceGroupsAPI } from '../../../api';
|
||||
|
||||
const getInstanceGroups = async (params) => InstanceGroupsAPI.read(params);
|
||||
@@ -66,4 +64,4 @@ InstanceGroupsLookup.defaultProps = {
|
||||
tooltip: '',
|
||||
};
|
||||
|
||||
export default withI18n()(withNetwork(InstanceGroupsLookup));
|
||||
export default withI18n()(InstanceGroupsLookup);
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
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/FormActionGroup';
|
||||
@@ -210,4 +209,4 @@ OrganizationForm.contextTypes = {
|
||||
};
|
||||
|
||||
export { OrganizationForm as _OrganizationForm };
|
||||
export default withI18n()(withNetwork(withRouter(OrganizationForm)));
|
||||
export default withI18n()(withRouter(OrganizationForm));
|
||||
|
||||
@@ -3,9 +3,8 @@ import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Switch, Route, withRouter, Redirect } from 'react-router-dom';
|
||||
import { Card, CardHeader, PageSection } from '@patternfly/react-core';
|
||||
import { withNetwork } from '../../../../contexts/Network';
|
||||
import NotifyAndRedirect from '../../../../components/NotifyAndRedirect';
|
||||
import CardCloseButton from '../../../../components/CardCloseButton';
|
||||
import ContentError from '../../../../components/ContentError';
|
||||
import OrganizationAccess from './OrganizationAccess';
|
||||
import OrganizationDetail from './OrganizationDetail';
|
||||
import OrganizationEdit from './OrganizationEdit';
|
||||
@@ -20,77 +19,74 @@ class Organization extends Component {
|
||||
|
||||
this.state = {
|
||||
organization: null,
|
||||
error: false,
|
||||
loading: true,
|
||||
contentLoading: true,
|
||||
contentError: false,
|
||||
isInitialized: false,
|
||||
isNotifAdmin: false,
|
||||
isAuditorOfThisOrg: false,
|
||||
isAdminOfThisOrg: false
|
||||
isAdminOfThisOrg: false,
|
||||
};
|
||||
|
||||
this.fetchOrganization = this.fetchOrganization.bind(this);
|
||||
this.fetchOrganizationAndRoles = this.fetchOrganizationAndRoles.bind(this);
|
||||
this.loadOrganization = this.loadOrganization.bind(this);
|
||||
this.loadOrganizationAndRoles = this.loadOrganizationAndRoles.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.fetchOrganizationAndRoles();
|
||||
async componentDidMount () {
|
||||
await this.loadOrganizationAndRoles();
|
||||
this.setState({ isInitialized: true });
|
||||
}
|
||||
|
||||
async componentDidUpdate (prevProps) {
|
||||
const { location } = this.props;
|
||||
if (location !== prevProps.location) {
|
||||
await this.fetchOrganization();
|
||||
await this.loadOrganization();
|
||||
}
|
||||
}
|
||||
|
||||
async fetchOrganizationAndRoles () {
|
||||
async loadOrganizationAndRoles () {
|
||||
const {
|
||||
match,
|
||||
setBreadcrumb,
|
||||
handleHttpError
|
||||
} = this.props;
|
||||
const id = parseInt(match.params.id, 10);
|
||||
|
||||
this.setState({ contentError: false, contentLoading: true });
|
||||
try {
|
||||
const [{ data }, notifAdminRest, auditorRes, adminRes] = await Promise.all([
|
||||
OrganizationsAPI.readDetail(parseInt(match.params.id, 10)),
|
||||
OrganizationsAPI.read({
|
||||
role_level: 'notification_admin_role',
|
||||
page_size: 1
|
||||
}),
|
||||
OrganizationsAPI.read({
|
||||
role_level: 'auditor_role',
|
||||
id: parseInt(match.params.id, 10)
|
||||
}),
|
||||
OrganizationsAPI.read({
|
||||
role_level: 'admin_role',
|
||||
id: parseInt(match.params.id, 10)
|
||||
})
|
||||
const [{ data }, notifAdminRes, auditorRes, adminRes] = await Promise.all([
|
||||
OrganizationsAPI.readDetail(id),
|
||||
OrganizationsAPI.read({ page_size: 1, role_level: 'notification_admin_role' }),
|
||||
OrganizationsAPI.read({ id, role_level: 'auditor_role' }),
|
||||
OrganizationsAPI.read({ id, role_level: 'admin_role' }),
|
||||
]);
|
||||
setBreadcrumb(data);
|
||||
this.setState({
|
||||
organization: data,
|
||||
loading: false,
|
||||
isNotifAdmin: notifAdminRest.data.results.length > 0,
|
||||
isNotifAdmin: notifAdminRes.data.results.length > 0,
|
||||
isAuditorOfThisOrg: auditorRes.data.results.length > 0,
|
||||
isAdminOfThisOrg: adminRes.data.results.length > 0
|
||||
});
|
||||
} catch (error) {
|
||||
handleHttpError(error) || this.setState({ error: true, loading: false });
|
||||
} catch (err) {
|
||||
this.setState(({ contentError: true }));
|
||||
} finally {
|
||||
this.setState({ contentLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
async fetchOrganization () {
|
||||
async loadOrganization () {
|
||||
const {
|
||||
match,
|
||||
setBreadcrumb,
|
||||
handleHttpError
|
||||
} = this.props;
|
||||
const id = parseInt(match.params.id, 10);
|
||||
|
||||
this.setState({ contentError: false, contentLoading: true });
|
||||
try {
|
||||
const { data } = await OrganizationsAPI.readDetail(parseInt(match.params.id, 10));
|
||||
const { data } = await OrganizationsAPI.readDetail(id);
|
||||
setBreadcrumb(data);
|
||||
this.setState({ organization: data, loading: false });
|
||||
} catch (error) {
|
||||
handleHttpError(error) || this.setState({ error: true, loading: false });
|
||||
this.setState({ organization: data });
|
||||
} catch (err) {
|
||||
this.setState(({ contentError: true }));
|
||||
} finally {
|
||||
this.setState({ contentLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,8 +101,9 @@ class Organization extends Component {
|
||||
|
||||
const {
|
||||
organization,
|
||||
error,
|
||||
loading,
|
||||
contentError,
|
||||
contentLoading,
|
||||
isInitialized,
|
||||
isNotifAdmin,
|
||||
isAuditorOfThisOrg,
|
||||
isAdminOfThisOrg
|
||||
@@ -134,25 +131,28 @@ class Organization extends Component {
|
||||
}
|
||||
|
||||
let cardHeader = (
|
||||
loading ? '' : (
|
||||
<CardHeader style={{ padding: 0 }}>
|
||||
<React.Fragment>
|
||||
<div className="awx-orgTabs-container">
|
||||
<RoutedTabs
|
||||
match={match}
|
||||
history={history}
|
||||
labeltext={i18n._(t`Organization detail tabs`)}
|
||||
tabsArray={tabsArray}
|
||||
/>
|
||||
<CardCloseButton linkTo="/organizations" />
|
||||
<div
|
||||
className="awx-orgTabs__bottom-border"
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</CardHeader>
|
||||
)
|
||||
<CardHeader style={{ padding: 0 }}>
|
||||
<React.Fragment>
|
||||
<div className="awx-orgTabs-container">
|
||||
<RoutedTabs
|
||||
match={match}
|
||||
history={history}
|
||||
labeltext={i18n._(t`Organization detail tabs`)}
|
||||
tabsArray={tabsArray}
|
||||
/>
|
||||
<CardCloseButton linkTo="/organizations" />
|
||||
<div
|
||||
className="awx-orgTabs__bottom-border"
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</CardHeader>
|
||||
);
|
||||
|
||||
if (!isInitialized) {
|
||||
cardHeader = null;
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
cardHeader = null;
|
||||
}
|
||||
@@ -161,10 +161,20 @@ class Organization extends Component {
|
||||
cardHeader = null;
|
||||
}
|
||||
|
||||
if (!contentLoading && contentError) {
|
||||
return (
|
||||
<PageSection>
|
||||
<Card className="awx-c-card">
|
||||
<ContentError />
|
||||
</Card>
|
||||
</PageSection>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageSection>
|
||||
<Card className="awx-c-card">
|
||||
{ cardHeader }
|
||||
{cardHeader}
|
||||
<Switch>
|
||||
<Redirect
|
||||
from="/organizations/:id"
|
||||
@@ -220,18 +230,12 @@ class Organization extends Component {
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{organization && (
|
||||
<NotifyAndRedirect
|
||||
to={`/organizations/${match.params.id}/details`}
|
||||
/>
|
||||
)}
|
||||
</Switch>
|
||||
{error ? 'error!' : ''}
|
||||
{loading ? 'loading...' : ''}
|
||||
</Card>
|
||||
</PageSection>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default withI18n()(withNetwork(withRouter(Organization)));
|
||||
|
||||
export default withI18n()(withRouter(Organization));
|
||||
export { Organization as _Organization };
|
||||
|
||||
@@ -2,13 +2,17 @@ import React, { Fragment } from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import AlertModal from '../../../../components/AlertModal';
|
||||
import PaginatedDataList, { ToolbarAddButton } from '../../../../components/PaginatedDataList';
|
||||
import DataListToolbar from '../../../../components/DataListToolbar';
|
||||
import OrganizationAccessItem from '../../components/OrganizationAccessItem';
|
||||
import DeleteRoleConfirmationModal from '../../components/DeleteRoleConfirmationModal';
|
||||
import AddResourceRole from '../../../../components/AddRole/AddResourceRole';
|
||||
import { withNetwork } from '../../../../contexts/Network';
|
||||
import { getQSConfig, parseNamespacedQueryString } from '../../../../util/qs';
|
||||
import {
|
||||
getQSConfig,
|
||||
encodeQueryString,
|
||||
parseNamespacedQueryString
|
||||
} from '../../../../util/qs';
|
||||
import { Organization } from '../../../../types';
|
||||
import { OrganizationsAPI, TeamsAPI, UsersAPI } from '../../../../api';
|
||||
|
||||
@@ -25,183 +29,191 @@ class OrganizationAccess extends React.Component {
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.readOrgAccessList = this.readOrgAccessList.bind(this);
|
||||
this.confirmRemoveRole = this.confirmRemoveRole.bind(this);
|
||||
this.cancelRemoveRole = this.cancelRemoveRole.bind(this);
|
||||
this.removeRole = this.removeRole.bind(this);
|
||||
this.toggleAddModal = this.toggleAddModal.bind(this);
|
||||
this.handleSuccessfulRoleAdd = this.handleSuccessfulRoleAdd.bind(this);
|
||||
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
isInitialized: false,
|
||||
isAddModalOpen: false,
|
||||
error: null,
|
||||
itemCount: 0,
|
||||
accessRecords: [],
|
||||
roleToDelete: null,
|
||||
roleToDeleteAccessRecord: null,
|
||||
contentError: false,
|
||||
contentLoading: true,
|
||||
deletionError: false,
|
||||
deletionRecord: null,
|
||||
deletionRole: null,
|
||||
isAddModalOpen: false,
|
||||
itemCount: 0,
|
||||
};
|
||||
this.loadAccessList = this.loadAccessList.bind(this);
|
||||
this.handleAddClose = this.handleAddClose.bind(this);
|
||||
this.handleAddOpen = this.handleAddOpen.bind(this);
|
||||
this.handleAddSuccess = this.handleAddSuccess.bind(this);
|
||||
this.handleDeleteCancel = this.handleDeleteCancel.bind(this);
|
||||
this.handleDeleteConfirm = this.handleDeleteConfirm.bind(this);
|
||||
this.handleDeleteErrorClose = this.handleDeleteErrorClose.bind(this);
|
||||
this.handleDeleteOpen = this.handleDeleteOpen.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.readOrgAccessList();
|
||||
this.loadAccessList();
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { location } = this.props;
|
||||
if (location !== prevProps.location) {
|
||||
this.readOrgAccessList();
|
||||
|
||||
const prevParams = parseNamespacedQueryString(QS_CONFIG, prevProps.location.search);
|
||||
const currentParams = parseNamespacedQueryString(QS_CONFIG, location.search);
|
||||
|
||||
if (encodeQueryString(currentParams) !== encodeQueryString(prevParams)) {
|
||||
this.loadAccessList();
|
||||
}
|
||||
}
|
||||
|
||||
async readOrgAccessList () {
|
||||
const { organization, handleHttpError, location } = this.props;
|
||||
this.setState({ isLoading: true });
|
||||
async loadAccessList () {
|
||||
const { organization, location } = this.props;
|
||||
const params = parseNamespacedQueryString(QS_CONFIG, location.search);
|
||||
|
||||
this.setState({ contentError: false, contentLoading: true });
|
||||
try {
|
||||
const { data } = await OrganizationsAPI.readAccessList(
|
||||
organization.id,
|
||||
parseNamespacedQueryString(QS_CONFIG, location.search)
|
||||
);
|
||||
this.setState({
|
||||
itemCount: data.count || 0,
|
||||
accessRecords: data.results || [],
|
||||
isLoading: false,
|
||||
isInitialized: true,
|
||||
});
|
||||
const {
|
||||
data: {
|
||||
results: accessRecords = [],
|
||||
count: itemCount = 0
|
||||
}
|
||||
} = await OrganizationsAPI.readAccessList(organization.id, params);
|
||||
this.setState({ itemCount, accessRecords });
|
||||
} catch (error) {
|
||||
handleHttpError(error) || this.setState({
|
||||
error,
|
||||
isLoading: false,
|
||||
});
|
||||
this.setState({ contentError: true });
|
||||
} finally {
|
||||
this.setState({ contentLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
confirmRemoveRole (role, accessRecord) {
|
||||
handleDeleteOpen (deletionRole, deletionRecord) {
|
||||
this.setState({ deletionRole, deletionRecord });
|
||||
}
|
||||
|
||||
handleDeleteCancel () {
|
||||
this.setState({ deletionRole: null, deletionRecord: null });
|
||||
}
|
||||
|
||||
handleDeleteErrorClose () {
|
||||
this.setState({
|
||||
roleToDelete: role,
|
||||
roleToDeleteAccessRecord: accessRecord,
|
||||
deletionError: false,
|
||||
deletionRecord: null,
|
||||
deletionRole: null
|
||||
});
|
||||
}
|
||||
|
||||
cancelRemoveRole () {
|
||||
this.setState({
|
||||
roleToDelete: null,
|
||||
roleToDeleteAccessRecord: null
|
||||
});
|
||||
}
|
||||
async handleDeleteConfirm () {
|
||||
const { deletionRole, deletionRecord } = this.state;
|
||||
|
||||
async removeRole () {
|
||||
const { handleHttpError } = this.props;
|
||||
const { roleToDelete: role, roleToDeleteAccessRecord: accessRecord } = this.state;
|
||||
if (!role || !accessRecord) {
|
||||
if (!deletionRole || !deletionRecord) {
|
||||
return;
|
||||
}
|
||||
const type = typeof role.team_id === 'undefined' ? 'users' : 'teams';
|
||||
this.setState({ isLoading: true });
|
||||
|
||||
let promise;
|
||||
if (typeof deletionRole.team_id !== 'undefined') {
|
||||
promise = TeamsAPI.disassociateRole(deletionRole.team_id, deletionRole.id);
|
||||
} else {
|
||||
promise = UsersAPI.disassociateRole(deletionRecord.id, deletionRole.id);
|
||||
}
|
||||
|
||||
this.setState({ contentLoading: true });
|
||||
try {
|
||||
if (type === 'teams') {
|
||||
await TeamsAPI.disassociateRole(role.team_id, role.id);
|
||||
} else {
|
||||
await UsersAPI.disassociateRole(accessRecord.id, role.id);
|
||||
}
|
||||
await promise.then(this.loadAccessList);
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
roleToDelete: null,
|
||||
roleToDeleteAccessRecord: null,
|
||||
deletionRole: null,
|
||||
deletionRecord: null
|
||||
});
|
||||
this.readOrgAccessList();
|
||||
} catch (error) {
|
||||
handleHttpError(error) || this.setState({
|
||||
error,
|
||||
isLoading: false,
|
||||
this.setState({
|
||||
contentLoading: false,
|
||||
deletionError: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
toggleAddModal () {
|
||||
const { isAddModalOpen } = this.state;
|
||||
this.setState({
|
||||
isAddModalOpen: !isAddModalOpen,
|
||||
});
|
||||
handleAddClose () {
|
||||
this.setState({ isAddModalOpen: false });
|
||||
}
|
||||
|
||||
handleSuccessfulRoleAdd () {
|
||||
this.toggleAddModal();
|
||||
this.readOrgAccessList();
|
||||
handleAddOpen () {
|
||||
this.setState({ isAddModalOpen: true });
|
||||
}
|
||||
|
||||
handleAddSuccess () {
|
||||
this.setState({ isAddModalOpen: false });
|
||||
this.loadAccessList();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { organization, i18n } = this.props;
|
||||
const {
|
||||
isLoading,
|
||||
isInitialized,
|
||||
accessRecords,
|
||||
contentError,
|
||||
contentLoading,
|
||||
deletionRole,
|
||||
deletionRecord,
|
||||
deletionError,
|
||||
itemCount,
|
||||
isAddModalOpen,
|
||||
accessRecords,
|
||||
roleToDelete,
|
||||
roleToDeleteAccessRecord,
|
||||
error,
|
||||
} = this.state;
|
||||
|
||||
const canEdit = organization.summary_fields.user_capabilities.edit;
|
||||
const isDeleteModalOpen = !contentLoading && !deletionError && deletionRole;
|
||||
|
||||
if (error) {
|
||||
// TODO: better error state
|
||||
return <div>{error.message}</div>;
|
||||
}
|
||||
// TODO: better loading state
|
||||
return (
|
||||
<Fragment>
|
||||
{isLoading && (<div>Loading...</div>)}
|
||||
{roleToDelete && (
|
||||
<DeleteRoleConfirmationModal
|
||||
role={roleToDelete}
|
||||
username={roleToDeleteAccessRecord.username}
|
||||
onCancel={this.cancelRemoveRole}
|
||||
onConfirm={this.removeRole}
|
||||
/>
|
||||
)}
|
||||
{isInitialized && (
|
||||
<PaginatedDataList
|
||||
items={accessRecords}
|
||||
itemCount={itemCount}
|
||||
itemName="role"
|
||||
qsConfig={QS_CONFIG}
|
||||
toolbarColumns={[
|
||||
{ name: i18n._(t`Name`), key: 'first_name', isSortable: true },
|
||||
{ name: i18n._(t`Username`), key: 'username', isSortable: true },
|
||||
{ name: i18n._(t`Last Name`), key: 'last_name', isSortable: true },
|
||||
]}
|
||||
renderToolbar={(props) => (
|
||||
<DataListToolbar
|
||||
{...props}
|
||||
additionalControls={canEdit ? [
|
||||
<ToolbarAddButton key="add" onClick={this.toggleAddModal} />
|
||||
] : null}
|
||||
/>
|
||||
)}
|
||||
renderItem={accessRecord => (
|
||||
<OrganizationAccessItem
|
||||
key={accessRecord.id}
|
||||
accessRecord={accessRecord}
|
||||
onRoleDelete={this.confirmRemoveRole}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<PaginatedDataList
|
||||
contentError={contentError}
|
||||
contentLoading={contentLoading}
|
||||
items={accessRecords}
|
||||
itemCount={itemCount}
|
||||
itemName="role"
|
||||
qsConfig={QS_CONFIG}
|
||||
toolbarColumns={[
|
||||
{ name: i18n._(t`Name`), key: 'first_name', isSortable: true },
|
||||
{ name: i18n._(t`Username`), key: 'username', isSortable: true },
|
||||
{ name: i18n._(t`Last Name`), key: 'last_name', isSortable: true },
|
||||
]}
|
||||
renderToolbar={(props) => (
|
||||
<DataListToolbar
|
||||
{...props}
|
||||
additionalControls={canEdit ? [
|
||||
<ToolbarAddButton key="add" onClick={this.handleAddOpen} />
|
||||
] : null}
|
||||
/>
|
||||
)}
|
||||
renderItem={accessRecord => (
|
||||
<OrganizationAccessItem
|
||||
key={accessRecord.id}
|
||||
accessRecord={accessRecord}
|
||||
onRoleDelete={this.handleDeleteOpen}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{isAddModalOpen && (
|
||||
<AddResourceRole
|
||||
onClose={this.toggleAddModal}
|
||||
onSave={this.handleSuccessfulRoleAdd}
|
||||
onClose={this.handleAddClose}
|
||||
onSave={this.handleAddSuccess}
|
||||
roles={organization.summary_fields.object_roles}
|
||||
/>
|
||||
)}
|
||||
{isDeleteModalOpen && (
|
||||
<DeleteRoleConfirmationModal
|
||||
role={deletionRole}
|
||||
username={deletionRecord.username}
|
||||
onCancel={this.handleDeleteCancel}
|
||||
onConfirm={this.handleDeleteConfirm}
|
||||
/>
|
||||
)}
|
||||
<AlertModal
|
||||
isOpen={deletionError}
|
||||
variant="danger"
|
||||
title={i18n._(t`Error!`)}
|
||||
onClose={this.handleDeleteErrorClose}
|
||||
>
|
||||
{i18n._(t`Failed to delete role`)}
|
||||
</AlertModal>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { OrganizationAccess as _OrganizationAccess };
|
||||
export default withI18n()(withNetwork(withRouter(OrganizationAccess)));
|
||||
export default withI18n()(withRouter(OrganizationAccess));
|
||||
|
||||
@@ -4,9 +4,11 @@ import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { CardBody as PFCardBody, Button } from '@patternfly/react-core';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { DetailList, Detail } from '../../../../components/DetailList';
|
||||
import { withNetwork } from '../../../../contexts/Network';
|
||||
import { ChipGroup, Chip } from '../../../../components/Chip';
|
||||
import ContentError from '../../../../components/ContentError';
|
||||
import ContentLoading from '../../../../components/ContentLoading';
|
||||
import { OrganizationsAPI } from '../../../../api';
|
||||
|
||||
const CardBody = styled(PFCardBody)`
|
||||
@@ -18,8 +20,9 @@ class OrganizationDetail extends Component {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
contentError: false,
|
||||
contentLoading: true,
|
||||
instanceGroups: [],
|
||||
error: false
|
||||
};
|
||||
this.loadInstanceGroups = this.loadInstanceGroups.bind(this);
|
||||
}
|
||||
@@ -29,25 +32,23 @@ class OrganizationDetail extends Component {
|
||||
}
|
||||
|
||||
async loadInstanceGroups () {
|
||||
const {
|
||||
handleHttpError,
|
||||
match
|
||||
} = this.props;
|
||||
const { match: { params: { id } } } = this.props;
|
||||
|
||||
this.setState({ contentLoading: true });
|
||||
try {
|
||||
const {
|
||||
data
|
||||
} = await OrganizationsAPI.readInstanceGroups(match.params.id);
|
||||
this.setState({
|
||||
instanceGroups: [...data.results]
|
||||
});
|
||||
const { data: { results = [] } } = await OrganizationsAPI.readInstanceGroups(id);
|
||||
this.setState({ instanceGroups: [...results] });
|
||||
} catch (err) {
|
||||
handleHttpError(err) || this.setState({ error: true });
|
||||
this.setState({ contentError: true });
|
||||
} finally {
|
||||
this.setState({ contentLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
error,
|
||||
contentLoading,
|
||||
contentError,
|
||||
instanceGroups,
|
||||
} = this.state;
|
||||
|
||||
@@ -65,6 +66,14 @@ class OrganizationDetail extends Component {
|
||||
i18n
|
||||
} = this.props;
|
||||
|
||||
if (contentLoading) {
|
||||
return (<ContentLoading />);
|
||||
}
|
||||
|
||||
if (contentError) {
|
||||
return (<ContentError />);
|
||||
}
|
||||
|
||||
return (
|
||||
<CardBody>
|
||||
<DetailList>
|
||||
@@ -116,10 +125,9 @@ class OrganizationDetail extends Component {
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{error ? 'error!' : ''}
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withI18n()(withRouter(withNetwork(OrganizationDetail)));
|
||||
export default withI18n()(withRouter(OrganizationDetail));
|
||||
|
||||
@@ -4,7 +4,7 @@ import { withRouter } from 'react-router-dom';
|
||||
import { CardBody } from '@patternfly/react-core';
|
||||
import OrganizationForm from '../../components/OrganizationForm';
|
||||
import { Config } from '../../../../contexts/Config';
|
||||
import { withNetwork } from '../../../../contexts/Network';
|
||||
|
||||
import { OrganizationsAPI } from '../../../../api';
|
||||
|
||||
class OrganizationEdit extends Component {
|
||||
@@ -22,13 +22,13 @@ class OrganizationEdit extends Component {
|
||||
}
|
||||
|
||||
async handleSubmit (values, groupsToAssociate, groupsToDisassociate) {
|
||||
const { organization, handleHttpError } = this.props;
|
||||
const { organization } = this.props;
|
||||
try {
|
||||
await OrganizationsAPI.update(organization.id, values);
|
||||
await this.submitInstanceGroups(groupsToAssociate, groupsToDisassociate);
|
||||
this.handleSuccess();
|
||||
} catch (err) {
|
||||
handleHttpError(err) || this.setState({ error: err });
|
||||
this.setState({ error: err });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,7 @@ class OrganizationEdit extends Component {
|
||||
}
|
||||
|
||||
async submitInstanceGroups (groupsToAssociate, groupsToDisassociate) {
|
||||
const { organization, handleHttpError } = this.props;
|
||||
|
||||
const { organization } = this.props;
|
||||
try {
|
||||
await Promise.all(
|
||||
groupsToAssociate.map(id => OrganizationsAPI.associateInstanceGroup(organization.id, id))
|
||||
@@ -55,7 +54,7 @@ class OrganizationEdit extends Component {
|
||||
)
|
||||
);
|
||||
} catch (err) {
|
||||
handleHttpError(err) || this.setState({ error: err });
|
||||
this.setState({ error: err });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,4 +89,4 @@ OrganizationEdit.contextTypes = {
|
||||
};
|
||||
|
||||
export { OrganizationEdit as _OrganizationEdit };
|
||||
export default withNetwork(withRouter(OrganizationEdit));
|
||||
export default withRouter(OrganizationEdit);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { number, shape, func, string, bool } from 'prop-types';
|
||||
import { number, shape, string, bool } from 'prop-types';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { withNetwork } from '../../../../contexts/Network';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import AlertModal from '../../../../components/AlertModal';
|
||||
import PaginatedDataList from '../../../../components/PaginatedDataList';
|
||||
import NotificationListItem from '../../../../components/NotificationsList/NotificationListItem';
|
||||
import { getQSConfig, parseNamespacedQueryString } from '../../../../util/qs';
|
||||
@@ -22,194 +25,159 @@ const COLUMNS = [
|
||||
class OrganizationNotifications extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.readNotifications = this.readNotifications.bind(this);
|
||||
this.readSuccessesAndErrors = this.readSuccessesAndErrors.bind(this);
|
||||
this.toggleNotification = this.toggleNotification.bind(this);
|
||||
|
||||
this.state = {
|
||||
isInitialized: false,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
contentError: false,
|
||||
contentLoading: true,
|
||||
toggleError: false,
|
||||
toggleLoading: false,
|
||||
itemCount: 0,
|
||||
notifications: [],
|
||||
successTemplateIds: [],
|
||||
errorTemplateIds: [],
|
||||
};
|
||||
this.handleNotificationToggle = this.handleNotificationToggle.bind(this);
|
||||
this.handleNotificationErrorClose = this.handleNotificationErrorClose.bind(this);
|
||||
this.loadNotifications = this.loadNotifications.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.readNotifications();
|
||||
this.loadNotifications();
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { location } = this.props;
|
||||
if (location !== prevProps.location) {
|
||||
this.readNotifications();
|
||||
this.loadNotifications();
|
||||
}
|
||||
}
|
||||
|
||||
async readNotifications () {
|
||||
const { id, handleHttpError, location } = this.props;
|
||||
async loadNotifications () {
|
||||
const { id, location } = this.props;
|
||||
const params = parseNamespacedQueryString(QS_CONFIG, location.search);
|
||||
this.setState({ isLoading: true });
|
||||
try {
|
||||
const { data } = await OrganizationsAPI.readNotificationTemplates(id, params);
|
||||
this.setState(
|
||||
{
|
||||
itemCount: data.count || 0,
|
||||
notifications: data.results || [],
|
||||
isLoading: false,
|
||||
isInitialized: true,
|
||||
},
|
||||
this.readSuccessesAndErrors
|
||||
);
|
||||
} catch (error) {
|
||||
handleHttpError(error) || this.setState({
|
||||
error,
|
||||
isLoading: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async readSuccessesAndErrors () {
|
||||
const { handleHttpError, id } = this.props;
|
||||
const { notifications } = this.state;
|
||||
if (!notifications.length) {
|
||||
return;
|
||||
}
|
||||
const ids = notifications.map(n => n.id).join(',');
|
||||
this.setState({ contentError: false, contentLoading: true });
|
||||
try {
|
||||
const successTemplatesPromise = OrganizationsAPI.readNotificationTemplatesSuccess(
|
||||
id,
|
||||
{ id__in: ids }
|
||||
);
|
||||
const errorTemplatesPromise = OrganizationsAPI.readNotificationTemplatesError(
|
||||
id,
|
||||
{ id__in: ids }
|
||||
);
|
||||
const {
|
||||
data: {
|
||||
count: itemCount = 0,
|
||||
results: notifications = [],
|
||||
}
|
||||
} = await OrganizationsAPI.readNotificationTemplates(id, params);
|
||||
|
||||
const { data: successTemplates } = await successTemplatesPromise;
|
||||
const { data: errorTemplates } = await errorTemplatesPromise;
|
||||
let idMatchParams;
|
||||
if (notifications.length > 0) {
|
||||
idMatchParams = { id__in: notifications.map(n => n.id).join(',') };
|
||||
} else {
|
||||
idMatchParams = {};
|
||||
}
|
||||
|
||||
const [
|
||||
{ data: successTemplates },
|
||||
{ data: errorTemplates },
|
||||
] = await Promise.all([
|
||||
OrganizationsAPI.readNotificationTemplatesSuccess(id, idMatchParams),
|
||||
OrganizationsAPI.readNotificationTemplatesError(id, idMatchParams),
|
||||
]);
|
||||
|
||||
this.setState({
|
||||
itemCount,
|
||||
notifications,
|
||||
successTemplateIds: successTemplates.results.map(s => s.id),
|
||||
errorTemplateIds: errorTemplates.results.map(e => e.id),
|
||||
});
|
||||
} catch (error) {
|
||||
handleHttpError(error) || this.setState({
|
||||
error,
|
||||
isLoading: false,
|
||||
} catch {
|
||||
this.setState({ contentError: true });
|
||||
} finally {
|
||||
this.setState({ contentLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
async handleNotificationToggle (notificationId, isCurrentlyOn, status) {
|
||||
const { id } = this.props;
|
||||
|
||||
let stateArrayName;
|
||||
if (status === 'success') {
|
||||
stateArrayName = 'successTemplateIds';
|
||||
} else {
|
||||
stateArrayName = 'errorTemplateIds';
|
||||
}
|
||||
|
||||
let stateUpdateFunction;
|
||||
if (isCurrentlyOn) {
|
||||
// when switching off, remove the toggled notification id from the array
|
||||
stateUpdateFunction = (prevState) => ({
|
||||
[stateArrayName]: prevState[stateArrayName].filter(i => i !== notificationId)
|
||||
});
|
||||
} else {
|
||||
// when switching on, add the toggled notification id to the array
|
||||
stateUpdateFunction = (prevState) => ({
|
||||
[stateArrayName]: prevState[stateArrayName].concat(notificationId)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
toggleNotification = (notificationId, isCurrentlyOn, status) => {
|
||||
if (status === 'success') {
|
||||
if (isCurrentlyOn) {
|
||||
this.disassociateSuccess(notificationId);
|
||||
} else {
|
||||
this.associateSuccess(notificationId);
|
||||
}
|
||||
} else if (status === 'error') {
|
||||
if (isCurrentlyOn) {
|
||||
this.disassociateError(notificationId);
|
||||
} else {
|
||||
this.associateError(notificationId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async associateSuccess (notificationId) {
|
||||
const { id, handleHttpError } = this.props;
|
||||
this.setState({ toggleLoading: true });
|
||||
try {
|
||||
await OrganizationsAPI.associateNotificationTemplatesSuccess(id, notificationId);
|
||||
this.setState(prevState => ({
|
||||
successTemplateIds: [...prevState.successTemplateIds, notificationId]
|
||||
}));
|
||||
await OrganizationsAPI.updateNotificationTemplateAssociation(
|
||||
id,
|
||||
notificationId,
|
||||
status,
|
||||
!isCurrentlyOn
|
||||
);
|
||||
this.setState(stateUpdateFunction);
|
||||
} catch (err) {
|
||||
handleHttpError(err) || this.setState({ error: true });
|
||||
this.setState({ toggleError: true });
|
||||
} finally {
|
||||
this.setState({ toggleLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
async disassociateSuccess (notificationId) {
|
||||
const { id, handleHttpError } = this.props;
|
||||
try {
|
||||
await OrganizationsAPI.disassociateNotificationTemplatesSuccess(id, notificationId);
|
||||
this.setState((prevState) => ({
|
||||
successTemplateIds: prevState.successTemplateIds
|
||||
.filter((templateId) => templateId !== notificationId)
|
||||
}));
|
||||
} catch (err) {
|
||||
handleHttpError(err) || this.setState({ error: true });
|
||||
}
|
||||
}
|
||||
|
||||
async associateError (notificationId) {
|
||||
const { id, handleHttpError } = this.props;
|
||||
try {
|
||||
await OrganizationsAPI.associateNotificationTemplatesError(id, notificationId);
|
||||
this.setState(prevState => ({
|
||||
errorTemplateIds: [...prevState.errorTemplateIds, notificationId]
|
||||
}));
|
||||
} catch (err) {
|
||||
handleHttpError(err) || this.setState({ error: true });
|
||||
}
|
||||
}
|
||||
|
||||
async disassociateError (notificationId) {
|
||||
const { id, handleHttpError } = this.props;
|
||||
try {
|
||||
await OrganizationsAPI.disassociateNotificationTemplatesError(id, notificationId);
|
||||
this.setState((prevState) => ({
|
||||
errorTemplateIds: prevState.errorTemplateIds
|
||||
.filter((templateId) => templateId !== notificationId)
|
||||
}));
|
||||
} catch (err) {
|
||||
handleHttpError(err) || this.setState({ error: true });
|
||||
}
|
||||
handleNotificationErrorClose () {
|
||||
this.setState({ toggleError: false });
|
||||
}
|
||||
|
||||
render () {
|
||||
const { canToggleNotifications } = this.props;
|
||||
const { canToggleNotifications, i18n } = this.props;
|
||||
const {
|
||||
notifications,
|
||||
contentError,
|
||||
contentLoading,
|
||||
toggleError,
|
||||
toggleLoading,
|
||||
itemCount,
|
||||
isLoading,
|
||||
isInitialized,
|
||||
error,
|
||||
notifications,
|
||||
successTemplateIds,
|
||||
errorTemplateIds,
|
||||
} = this.state;
|
||||
|
||||
if (error) {
|
||||
// TODO: better error state
|
||||
return <div>{error.message}</div>;
|
||||
}
|
||||
// TODO: better loading state
|
||||
return (
|
||||
<Fragment>
|
||||
{isLoading && (<div>Loading...</div>)}
|
||||
{isInitialized && (
|
||||
<PaginatedDataList
|
||||
items={notifications}
|
||||
itemCount={itemCount}
|
||||
itemName="notification"
|
||||
qsConfig={QS_CONFIG}
|
||||
toolbarColumns={COLUMNS}
|
||||
renderItem={(notification) => (
|
||||
<NotificationListItem
|
||||
key={notification.id}
|
||||
notification={notification}
|
||||
detailUrl={`/notifications/${notification.id}`}
|
||||
canToggleNotifications={canToggleNotifications}
|
||||
toggleNotification={this.toggleNotification}
|
||||
errorTurnedOn={errorTemplateIds.includes(notification.id)}
|
||||
successTurnedOn={successTemplateIds.includes(notification.id)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<PaginatedDataList
|
||||
contentError={contentError}
|
||||
contentLoading={contentLoading}
|
||||
items={notifications}
|
||||
itemCount={itemCount}
|
||||
itemName="notification"
|
||||
qsConfig={QS_CONFIG}
|
||||
toolbarColumns={COLUMNS}
|
||||
renderItem={(notification) => (
|
||||
<NotificationListItem
|
||||
key={notification.id}
|
||||
notification={notification}
|
||||
detailUrl={`/notifications/${notification.id}`}
|
||||
canToggleNotifications={canToggleNotifications && !toggleLoading}
|
||||
toggleNotification={this.handleNotificationToggle}
|
||||
errorTurnedOn={errorTemplateIds.includes(notification.id)}
|
||||
successTurnedOn={successTemplateIds.includes(notification.id)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<AlertModal
|
||||
variant="danger"
|
||||
title={i18n._(t`Error!`)}
|
||||
isOpen={toggleError && !toggleLoading}
|
||||
onClose={this.handleNotificationErrorClose}
|
||||
>
|
||||
{i18n._(t`Failed to toggle notification.`)}
|
||||
</AlertModal>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -218,11 +186,10 @@ class OrganizationNotifications extends Component {
|
||||
OrganizationNotifications.propTypes = {
|
||||
id: number.isRequired,
|
||||
canToggleNotifications: bool.isRequired,
|
||||
handleHttpError: func.isRequired,
|
||||
location: shape({
|
||||
search: string.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export { OrganizationNotifications as _OrganizationNotifications };
|
||||
export default withNetwork(withRouter(OrganizationNotifications));
|
||||
export default withI18n()(withRouter(OrganizationNotifications));
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import PaginatedDataList from '../../../../components/PaginatedDataList';
|
||||
import { getQSConfig, parseNamespacedQueryString } from '../../../../util/qs';
|
||||
import { withNetwork } from '../../../../contexts/Network';
|
||||
import { OrganizationsAPI } from '../../../../api';
|
||||
|
||||
const QS_CONFIG = getQSConfig('team', {
|
||||
@@ -16,32 +15,32 @@ class OrganizationTeams extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.readOrganizationTeamsList = this.readOrganizationTeamsList.bind(this);
|
||||
this.loadOrganizationTeamsList = this.loadOrganizationTeamsList.bind(this);
|
||||
|
||||
this.state = {
|
||||
isInitialized: false,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
contentError: false,
|
||||
contentLoading: true,
|
||||
itemCount: 0,
|
||||
teams: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.readOrganizationTeamsList();
|
||||
this.loadOrganizationTeamsList();
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { location } = this.props;
|
||||
if (location !== prevProps.location) {
|
||||
this.readOrganizationTeamsList();
|
||||
this.loadOrganizationTeamsList();
|
||||
}
|
||||
}
|
||||
|
||||
async readOrganizationTeamsList () {
|
||||
const { id, handleHttpError, location } = this.props;
|
||||
async loadOrganizationTeamsList () {
|
||||
const { id, location } = this.props;
|
||||
const params = parseNamespacedQueryString(QS_CONFIG, location.search);
|
||||
this.setState({ isLoading: true, error: null });
|
||||
|
||||
this.setState({ contentLoading: true, contentError: false });
|
||||
try {
|
||||
const {
|
||||
data: { count = 0, results = [] },
|
||||
@@ -49,38 +48,25 @@ class OrganizationTeams extends React.Component {
|
||||
this.setState({
|
||||
itemCount: count,
|
||||
teams: results,
|
||||
isLoading: false,
|
||||
isInitialized: true,
|
||||
});
|
||||
} catch (error) {
|
||||
handleHttpError(error) || this.setState({
|
||||
error,
|
||||
isLoading: false,
|
||||
});
|
||||
} catch {
|
||||
this.setState({ contentError: true });
|
||||
} finally {
|
||||
this.setState({ contentLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { teams, itemCount, isLoading, isInitialized, error } = this.state;
|
||||
|
||||
if (error) {
|
||||
// TODO: better error state
|
||||
return <div>{error.message}</div>;
|
||||
}
|
||||
|
||||
// TODO: better loading state
|
||||
const { contentError, contentLoading, teams, itemCount } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
{isLoading && (<div>Loading...</div>)}
|
||||
{isInitialized && (
|
||||
<PaginatedDataList
|
||||
items={teams}
|
||||
itemCount={itemCount}
|
||||
itemName="team"
|
||||
qsConfig={QS_CONFIG}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
<PaginatedDataList
|
||||
contentError={contentError}
|
||||
contentLoading={contentLoading}
|
||||
items={teams}
|
||||
itemCount={itemCount}
|
||||
itemName="team"
|
||||
qsConfig={QS_CONFIG}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -90,4 +76,4 @@ OrganizationTeams.propTypes = {
|
||||
};
|
||||
|
||||
export { OrganizationTeams as _OrganizationTeams };
|
||||
export default withNetwork(withRouter(OrganizationTeams));
|
||||
export default withRouter(OrganizationTeams);
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import { Config } from '../../../contexts/Config';
|
||||
import { withNetwork } from '../../../contexts/Network';
|
||||
import CardCloseButton from '../../../components/CardCloseButton';
|
||||
import OrganizationForm from '../components/OrganizationForm';
|
||||
import { OrganizationsAPI } from '../../../api';
|
||||
@@ -20,29 +19,20 @@ import { OrganizationsAPI } from '../../../api';
|
||||
class OrganizationAdd extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleCancel = this.handleCancel.bind(this);
|
||||
this.handleSuccess = this.handleSuccess.bind(this);
|
||||
|
||||
this.state = {
|
||||
error: '',
|
||||
};
|
||||
this.state = { error: '' };
|
||||
}
|
||||
|
||||
async handleSubmit (values, groupsToAssociate) {
|
||||
const { handleHttpError } = this.props;
|
||||
const { history } = this.props;
|
||||
try {
|
||||
const { data: response } = await OrganizationsAPI.create(values);
|
||||
try {
|
||||
await Promise.all(groupsToAssociate.map(id => OrganizationsAPI
|
||||
.associateInstanceGroup(response.id, id)));
|
||||
this.handleSuccess(response.id);
|
||||
} catch (err) {
|
||||
handleHttpError(err) || this.setState({ error: err });
|
||||
}
|
||||
} catch (err) {
|
||||
this.setState({ error: err });
|
||||
await Promise.all(groupsToAssociate.map(id => OrganizationsAPI
|
||||
.associateInstanceGroup(response.id, id)));
|
||||
history.push(`/organizations/${response.id}`);
|
||||
} catch (error) {
|
||||
this.setState({ error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,11 +41,6 @@ class OrganizationAdd extends React.Component {
|
||||
history.push('/organizations');
|
||||
}
|
||||
|
||||
handleSuccess (id) {
|
||||
const { history } = this.props;
|
||||
history.push(`/organizations/${id}`);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { error } = this.state;
|
||||
const { i18n } = this.props;
|
||||
@@ -94,4 +79,4 @@ OrganizationAdd.contextTypes = {
|
||||
};
|
||||
|
||||
export { OrganizationAdd as _OrganizationAdd };
|
||||
export default withI18n()(withNetwork(withRouter(OrganizationAdd)));
|
||||
export default withI18n()(withRouter(OrganizationAdd));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Component } from 'react';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
@@ -8,13 +8,13 @@ import {
|
||||
PageSectionVariants,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import { withNetwork } from '../../../contexts/Network';
|
||||
import PaginatedDataList, {
|
||||
ToolbarDeleteButton,
|
||||
ToolbarAddButton
|
||||
} from '../../../components/PaginatedDataList';
|
||||
import DataListToolbar from '../../../components/DataListToolbar';
|
||||
import OrganizationListItem from '../components/OrganizationListItem';
|
||||
import AlertModal from '../../../components/AlertModal';
|
||||
import { getQSConfig, parseNamespacedQueryString } from '../../../util/qs';
|
||||
import { OrganizationsAPI } from '../../../api';
|
||||
|
||||
@@ -29,29 +29,30 @@ class OrganizationsList extends Component {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
error: null,
|
||||
isLoading: true,
|
||||
isInitialized: false,
|
||||
contentLoading: true,
|
||||
contentError: false,
|
||||
deletionError: false,
|
||||
organizations: [],
|
||||
selected: []
|
||||
selected: [],
|
||||
itemCount: 0,
|
||||
actions: null,
|
||||
};
|
||||
|
||||
this.handleSelectAll = this.handleSelectAll.bind(this);
|
||||
this.handleSelect = this.handleSelect.bind(this);
|
||||
this.fetchOptionsOrganizations = this.fetchOptionsOrganizations.bind(this);
|
||||
this.fetchOrganizations = this.fetchOrganizations.bind(this);
|
||||
this.handleOrgDelete = this.handleOrgDelete.bind(this);
|
||||
this.handleDeleteErrorClose = this.handleDeleteErrorClose.bind(this);
|
||||
this.loadOrganizations = this.loadOrganizations.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.fetchOptionsOrganizations();
|
||||
this.fetchOrganizations();
|
||||
this.loadOrganizations();
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { location } = this.props;
|
||||
if (location !== prevProps.location) {
|
||||
this.fetchOrganizations();
|
||||
this.loadOrganizations();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,63 +73,54 @@ class OrganizationsList extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleDeleteErrorClose () {
|
||||
this.setState({ deletionError: false });
|
||||
}
|
||||
|
||||
async handleOrgDelete () {
|
||||
const { selected } = this.state;
|
||||
const { handleHttpError } = this.props;
|
||||
let errorHandled;
|
||||
|
||||
this.setState({ contentLoading: true, deletionError: false });
|
||||
try {
|
||||
await Promise.all(selected.map((org) => OrganizationsAPI.destroy(org.id)));
|
||||
this.setState({
|
||||
selected: []
|
||||
});
|
||||
this.setState({ selected: [] });
|
||||
} catch (err) {
|
||||
errorHandled = handleHttpError(err);
|
||||
this.setState({ deletionError: true });
|
||||
} finally {
|
||||
if (!errorHandled) {
|
||||
this.fetchOrganizations();
|
||||
}
|
||||
await this.loadOrganizations();
|
||||
}
|
||||
}
|
||||
|
||||
async fetchOrganizations () {
|
||||
const { handleHttpError, location } = this.props;
|
||||
async loadOrganizations () {
|
||||
const { location } = this.props;
|
||||
const { actions: cachedActions } = this.state;
|
||||
const params = parseNamespacedQueryString(QS_CONFIG, location.search);
|
||||
|
||||
this.setState({ error: false, isLoading: true });
|
||||
let optionsPromise;
|
||||
if (cachedActions) {
|
||||
optionsPromise = Promise.resolve({ data: { actions: cachedActions } });
|
||||
} else {
|
||||
optionsPromise = OrganizationsAPI.readOptions();
|
||||
}
|
||||
|
||||
const promises = Promise.all([
|
||||
OrganizationsAPI.read(params),
|
||||
optionsPromise,
|
||||
]);
|
||||
|
||||
this.setState({ contentError: false, contentLoading: true });
|
||||
try {
|
||||
const { data } = await OrganizationsAPI.read(params);
|
||||
const { count, results } = data;
|
||||
|
||||
const stateToUpdate = {
|
||||
const [{ data: { count, results } }, { data: { actions } }] = await promises;
|
||||
this.setState({
|
||||
actions,
|
||||
itemCount: count,
|
||||
organizations: results,
|
||||
selected: [],
|
||||
isLoading: false,
|
||||
isInitialized: true,
|
||||
};
|
||||
|
||||
this.setState(stateToUpdate);
|
||||
});
|
||||
} catch (err) {
|
||||
handleHttpError(err) || this.setState({ error: true, isLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
async fetchOptionsOrganizations () {
|
||||
try {
|
||||
const { data } = await OrganizationsAPI.readOptions();
|
||||
const { actions } = data;
|
||||
|
||||
const stateToUpdate = {
|
||||
canAdd: Object.prototype.hasOwnProperty.call(actions, 'POST')
|
||||
};
|
||||
|
||||
this.setState(stateToUpdate);
|
||||
} catch (err) {
|
||||
this.setState({ error: true });
|
||||
this.setState(({ contentError: true }));
|
||||
} finally {
|
||||
this.setState({ isLoading: false });
|
||||
this.setState({ contentLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,23 +129,26 @@ class OrganizationsList extends Component {
|
||||
medium,
|
||||
} = PageSectionVariants;
|
||||
const {
|
||||
canAdd,
|
||||
actions,
|
||||
itemCount,
|
||||
error,
|
||||
isLoading,
|
||||
isInitialized,
|
||||
contentError,
|
||||
contentLoading,
|
||||
deletionError,
|
||||
selected,
|
||||
organizations
|
||||
organizations,
|
||||
} = this.state;
|
||||
const { match, i18n } = this.props;
|
||||
|
||||
const canAdd = actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
||||
const isAllSelected = selected.length === organizations.length;
|
||||
|
||||
return (
|
||||
<PageSection variant={medium}>
|
||||
<Card>
|
||||
{isInitialized && (
|
||||
<Fragment>
|
||||
<PageSection variant={medium}>
|
||||
<Card>
|
||||
<PaginatedDataList
|
||||
contentError={contentError}
|
||||
contentLoading={contentLoading}
|
||||
items={organizations}
|
||||
itemCount={itemCount}
|
||||
itemName="organization"
|
||||
@@ -196,14 +191,20 @@ class OrganizationsList extends Component {
|
||||
: null
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{ isLoading ? <div>loading...</div> : '' }
|
||||
{ error ? <div>error</div> : '' }
|
||||
</Card>
|
||||
</PageSection>
|
||||
</Card>
|
||||
</PageSection>
|
||||
<AlertModal
|
||||
isOpen={deletionError}
|
||||
variant="danger"
|
||||
title={i18n._(t`Error!`)}
|
||||
onClose={this.handleDeleteErrorClose}
|
||||
>
|
||||
{i18n._(t`Failed to delete one or more organizations.`)}
|
||||
</AlertModal>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { OrganizationsList as _OrganizationsList };
|
||||
export default withI18n()(withNetwork(withRouter(OrganizationsList)));
|
||||
export default withI18n()(withRouter(OrganizationsList));
|
||||
|
||||
Reference in New Issue
Block a user