diff --git a/awx/ui_next/src/components/Schedule/ScheduleDetail/ScheduleDetail.jsx b/awx/ui_next/src/components/Schedule/ScheduleDetail/ScheduleDetail.jsx
index a63380083a..354a283b09 100644
--- a/awx/ui_next/src/components/Schedule/ScheduleDetail/ScheduleDetail.jsx
+++ b/awx/ui_next/src/components/Schedule/ScheduleDetail/ScheduleDetail.jsx
@@ -1,5 +1,5 @@
import 'styled-components/macro';
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useCallback, useEffect } from 'react';
import { Link, useHistory, useLocation } from 'react-router-dom';
import { RRule, rrulestr } from 'rrule';
import styled from 'styled-components';
@@ -16,7 +16,7 @@ import { DetailList, Detail, UserDateDetail } from '../../DetailList';
import ScheduleOccurrences from '../ScheduleOccurrences';
import ScheduleToggle from '../ScheduleToggle';
import { formatDateString } from '../../../util/dates';
-import useRequest from '../../../util/useRequest';
+import useRequest, { useDismissableError } from '../../../util/useRequest';
import { SchedulesAPI } from '../../../api';
import DeleteButton from '../../DeleteButton';
import ErrorDetail from '../../ErrorDetail';
@@ -48,27 +48,27 @@ function ScheduleDetail({ schedule, i18n }) {
timezone,
} = schedule;
- const [deletionError, setDeletionError] = useState(null);
- const [hasContentLoading, setHasContentLoading] = useState(false);
const history = useHistory();
const { pathname } = useLocation();
const pathRoot = pathname.substr(0, pathname.indexOf('schedules'));
- const handleDelete = async () => {
- setHasContentLoading(true);
- try {
+ const {
+ request: deleteSchedule,
+ isLoading: isDeleteLoading,
+ error: deleteError,
+ } = useRequest(
+ useCallback(async () => {
await SchedulesAPI.destroy(id);
history.push(`${pathRoot}schedules`);
- } catch (error) {
- setDeletionError(error);
- }
- setHasContentLoading(false);
- };
+ }, [id, history, pathRoot])
+ );
+
+ const { error, dismissError } = useDismissableError(deleteError);
const {
result: [credentials, preview],
isLoading,
- error,
+ error: readContentError,
request: fetchCredentialsAndPreview,
} = useRequest(
useCallback(async () => {
@@ -102,12 +102,12 @@ function ScheduleDetail({ schedule, i18n }) {
(job_tags && job_tags.length > 0) ||
(skip_tags && skip_tags.length > 0);
- if (isLoading || hasContentLoading) {
+ if (isLoading) {
return ;
}
- if (error) {
- return ;
+ if (readContentError) {
+ return ;
}
return (
@@ -237,21 +237,22 @@ function ScheduleDetail({ schedule, i18n }) {
{i18n._(t`Delete`)}
)}
- {deletionError && (
+ {error && (
setDeletionError(null)}
+ onClose={dismissError}
>
{i18n._(t`Failed to delete schedule.`)}
-
+
)}
diff --git a/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.jsx b/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.jsx
index 18b01bd0f9..2e8809955f 100644
--- a/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.jsx
+++ b/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.jsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
@@ -18,6 +18,7 @@ import {
import ErrorDetail from '../../../components/ErrorDetail';
import { CredentialsAPI, CredentialTypesAPI } from '../../../api';
import { Credential } from '../../../types';
+import useRequest, { useDismissableError } from '../../../util/useRequest';
function CredentialDetail({ i18n, credential }) {
const {
@@ -39,7 +40,6 @@ function CredentialDetail({ i18n, credential }) {
const [fields, setFields] = useState([]);
const [managedByTower, setManagedByTower] = useState([]);
const [contentError, setContentError] = useState(null);
- const [deletionError, setDeletionError] = useState(null);
const [hasContentLoading, setHasContentLoading] = useState(true);
const history = useHistory();
@@ -62,17 +62,18 @@ function CredentialDetail({ i18n, credential }) {
})();
}, [credential_type]);
- const handleDelete = async () => {
- setHasContentLoading(true);
-
- try {
+ const {
+ request: deleteCredential,
+ isLoading,
+ error: deleteError,
+ } = useRequest(
+ useCallback(async () => {
await CredentialsAPI.destroy(credentialId);
history.push('/credentials');
- } catch (error) {
- setDeletionError(error);
- }
- setHasContentLoading(false);
- };
+ }, [credentialId, history])
+ );
+
+ const { error, dismissError } = useDismissableError(deleteError);
const renderDetail = ({ id, label, type }) => {
let detail;
@@ -161,21 +162,22 @@ function CredentialDetail({ i18n, credential }) {
{i18n._(t`Delete`)}
)}
- {deletionError && (
+ {error && (
setDeletionError(null)}
+ onClose={dismissError}
>
{i18n._(t`Failed to delete credential.`)}
-
+
)}
diff --git a/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx b/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx
index ff6bd42012..522607e6d7 100644
--- a/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx
+++ b/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, useCallback } from 'react';
import { Link, useHistory, useRouteMatch } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
@@ -16,6 +16,7 @@ import ContentError from '../../../components/ContentError';
import ContentLoading from '../../../components/ContentLoading';
import DeleteButton from '../../../components/DeleteButton';
import ErrorDetail from '../../../components/ErrorDetail';
+import useRequest, { useDismissableError } from '../../../util/useRequest';
function OrganizationDetail({ i18n, organization }) {
const {
@@ -31,7 +32,6 @@ function OrganizationDetail({ i18n, organization }) {
summary_fields,
} = organization;
const [contentError, setContentError] = useState(null);
- const [deletionError, setDeletionError] = useState(null);
const [hasContentLoading, setHasContentLoading] = useState(true);
const [instanceGroups, setInstanceGroups] = useState([]);
const history = useHistory();
@@ -53,16 +53,18 @@ function OrganizationDetail({ i18n, organization }) {
})();
}, [id]);
- const handleDelete = async () => {
- setHasContentLoading(true);
- try {
+ const {
+ request: deleteOrganization,
+ isLoading,
+ error: deleteError,
+ } = useRequest(
+ useCallback(async () => {
await OrganizationsAPI.destroy(id);
history.push(`/organizations`);
- } catch (error) {
- setDeletionError(error);
- }
- setHasContentLoading(false);
- };
+ }, [id, history])
+ );
+
+ const { error, dismissError } = useDismissableError(deleteError);
if (hasContentLoading) {
return ;
@@ -127,22 +129,23 @@ function OrganizationDetail({ i18n, organization }) {
{i18n._(t`Delete`)}
)}
{/* Update delete modal to show dependencies https://github.com/ansible/awx/issues/5546 */}
- {deletionError && (
+ {error && (
setDeletionError(null)}
+ onClose={dismissError}
>
{i18n._(t`Failed to delete organization.`)}
-
+
)}
diff --git a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx
index a549c7adba..380196d950 100644
--- a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx
+++ b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useCallback } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
@@ -8,7 +8,6 @@ import { Config } from '../../../contexts/Config';
import AlertModal from '../../../components/AlertModal';
import { CardBody, CardActionsRow } from '../../../components/Card';
-import ContentLoading from '../../../components/ContentLoading';
import DeleteButton from '../../../components/DeleteButton';
import {
DetailList,
@@ -19,6 +18,7 @@ import ErrorDetail from '../../../components/ErrorDetail';
import CredentialChip from '../../../components/CredentialChip';
import { ProjectsAPI } from '../../../api';
import { toTitleCase } from '../../../util/strings';
+import useRequest, { useDismissableError } from '../../../util/useRequest';
function ProjectDetail({ project, i18n }) {
const {
@@ -40,20 +40,16 @@ function ProjectDetail({ project, i18n }) {
scm_url,
summary_fields,
} = project;
- const [deletionError, setDeletionError] = useState(null);
- const [hasContentLoading, setHasContentLoading] = useState(false);
const history = useHistory();
- const handleDelete = async () => {
- setHasContentLoading(true);
- try {
+ const { request: deleteProject, isLoading, error: deleteError } = useRequest(
+ useCallback(async () => {
await ProjectsAPI.destroy(id);
history.push(`/projects`);
- } catch (error) {
- setDeletionError(error);
- }
- setHasContentLoading(false);
- };
+ }, [id, history])
+ );
+
+ const { error, dismissError } = useDismissableError(deleteError);
let optionsList = '';
if (
@@ -78,10 +74,6 @@ function ProjectDetail({ project, i18n }) {
);
}
- if (hasContentLoading) {
- return ;
- }
-
return (
@@ -171,22 +163,23 @@ function ProjectDetail({ project, i18n }) {
{i18n._(t`Delete`)}
)}
{/* Update delete modal to show dependencies https://github.com/ansible/awx/issues/5546 */}
- {deletionError && (
+ {error && (
setDeletionError(null)}
+ onClose={dismissError}
>
{i18n._(t`Failed to delete project.`)}
-
+
)}
diff --git a/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.jsx b/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.jsx
index f607800fcd..8347c1c735 100644
--- a/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.jsx
+++ b/awx/ui_next/src/screens/Team/TeamDetail/TeamDetail.jsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useCallback } from 'react';
import { Link, useHistory, useParams } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
@@ -6,34 +6,26 @@ import { Button } from '@patternfly/react-core';
import AlertModal from '../../../components/AlertModal';
import { CardBody, CardActionsRow } from '../../../components/Card';
-import ContentLoading from '../../../components/ContentLoading';
import DeleteButton from '../../../components/DeleteButton';
import { DetailList, Detail } from '../../../components/DetailList';
import ErrorDetail from '../../../components/ErrorDetail';
import { formatDateString } from '../../../util/dates';
import { TeamsAPI } from '../../../api';
+import useRequest, { useDismissableError } from '../../../util/useRequest';
function TeamDetail({ team, i18n }) {
const { name, description, created, modified, summary_fields } = team;
- const [deletionError, setDeletionError] = useState(null);
- const [hasContentLoading, setHasContentLoading] = useState(false);
const history = useHistory();
const { id } = useParams();
- const handleDelete = async () => {
- setHasContentLoading(true);
- try {
+ const { request: deleteTeam, isLoading, error: deleteError } = useRequest(
+ useCallback(async () => {
await TeamsAPI.destroy(id);
history.push(`/teams`);
- } catch (error) {
- setDeletionError(error);
- }
- setHasContentLoading(false);
- };
+ }, [id, history])
+ );
- if (hasContentLoading) {
- return ;
- }
+ const { error, dismissError } = useDismissableError(deleteError);
return (
@@ -74,21 +66,22 @@ function TeamDetail({ team, i18n }) {
{i18n._(t`Delete`)}
)}
- {deletionError && (
+ {error && (
setDeletionError(null)}
+ onClose={dismissError}
>
{i18n._(t`Failed to delete team.`)}
-
+
)}
diff --git a/awx/ui_next/src/screens/User/UserDetail/UserDetail.jsx b/awx/ui_next/src/screens/User/UserDetail/UserDetail.jsx
index aa2889921a..e4c0a1adfd 100644
--- a/awx/ui_next/src/screens/User/UserDetail/UserDetail.jsx
+++ b/awx/ui_next/src/screens/User/UserDetail/UserDetail.jsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useCallback } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
@@ -6,12 +6,12 @@ import { t } from '@lingui/macro';
import { Button } from '@patternfly/react-core';
import AlertModal from '../../../components/AlertModal';
import { CardBody, CardActionsRow } from '../../../components/Card';
-import ContentLoading from '../../../components/ContentLoading';
import DeleteButton from '../../../components/DeleteButton';
import { DetailList, Detail } from '../../../components/DetailList';
import ErrorDetail from '../../../components/ErrorDetail';
import { formatDateString } from '../../../util/dates';
import { UsersAPI } from '../../../api';
+import useRequest, { useDismissableError } from '../../../util/useRequest';
function UserDetail({ user, i18n }) {
const {
@@ -26,20 +26,16 @@ function UserDetail({ user, i18n }) {
is_system_auditor,
summary_fields,
} = user;
- const [deletionError, setDeletionError] = useState(null);
- const [hasContentLoading, setHasContentLoading] = useState(false);
const history = useHistory();
- const handleDelete = async () => {
- setHasContentLoading(true);
- try {
+ const { request: deleteUser, isLoading, error: deleteError } = useRequest(
+ useCallback(async () => {
await UsersAPI.destroy(id);
history.push(`/users`);
- } catch (error) {
- setDeletionError(error);
- }
- setHasContentLoading(false);
- };
+ }, [id, history])
+ );
+
+ const { error, dismissError } = useDismissableError(deleteError);
let user_type;
if (is_superuser) {
@@ -50,10 +46,6 @@ function UserDetail({ user, i18n }) {
user_type = i18n._(t`Normal User`);
}
- if (hasContentLoading) {
- return ;
- }
-
return (
@@ -90,21 +82,22 @@ function UserDetail({ user, i18n }) {
{i18n._(t`Delete`)}
)}
- {deletionError && (
+ {error && (
setDeletionError(null)}
+ onClose={dismissError}
>
{i18n._(t`Failed to delete user.`)}
-
+
)}