diff --git a/awx/ui_next/src/components/ErrorDetail/ErrorDetail.jsx b/awx/ui_next/src/components/ErrorDetail/ErrorDetail.jsx
index 1ef3cabc99..f1c309e959 100644
--- a/awx/ui_next/src/components/ErrorDetail/ErrorDetail.jsx
+++ b/awx/ui_next/src/components/ErrorDetail/ErrorDetail.jsx
@@ -54,7 +54,7 @@ class ErrorDetail extends Component {
const { response } = error;
let message = '';
- if (response.data) {
+ if (response?.data) {
message =
typeof response.data === 'string'
? response.data
@@ -64,8 +64,8 @@ class ErrorDetail extends Component {
return (
- {response.config.method.toUpperCase()} {response.config.url}{' '}
- {response.status}
+ {response?.config?.method.toUpperCase()} {response?.config?.url}{' '}
+ {response?.status}
{message}
diff --git a/awx/ui_next/src/screens/Template/TemplateSurvey.jsx b/awx/ui_next/src/screens/Template/TemplateSurvey.jsx
index 9c24b29439..bb19f04df4 100644
--- a/awx/ui_next/src/screens/Template/TemplateSurvey.jsx
+++ b/awx/ui_next/src/screens/Template/TemplateSurvey.jsx
@@ -1,56 +1,104 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
import { Switch, Route } from 'react-router-dom';
+import { withI18n } from '@lingui/react';
+import { t } from '@lingui/macro';
import { JobTemplatesAPI } from '@api';
+import ContentError from '@components/ContentError';
+import AlertModal from '@components/AlertModal';
+import ErrorDetail from '@components/ErrorDetail';
+import useRequest, { useDismissableError } from '@util/useRequest';
import SurveyList from './shared/SurveyList';
-export default function TemplateSurvey({ template }) {
- const [survey, setSurvey] = useState(null);
+function TemplateSurvey({ template, i18n }) {
const [surveyEnabled, setSurveyEnabled] = useState(template.survey_enabled);
- useEffect(() => {
- (async () => {
+ const {
+ result: survey,
+ request: fetchSurvey,
+ isLoading,
+ error: loadingError,
+ setValue: setSurvey,
+ } = useRequest(
+ useCallback(async () => {
const { data } = await JobTemplatesAPI.readSurvey(template.id);
- setSurvey(data);
- })();
- }, [template.id]);
+ return data;
+ }, [template.id])
+ );
+ useEffect(() => {
+ fetchSurvey();
+ }, [fetchSurvey]);
- const updateSurvey = async newQuestions => {
- await JobTemplatesAPI.updateSurvey(template.id, {
- ...survey,
- spec: newQuestions,
- });
- setSurvey({
- ...survey,
- spec: newQuestions,
- });
- };
+ const {
+ request: updateSurvey,
+ isLoading: isUpdateLoading,
+ error: updateError,
+ } = useRequest(
+ useCallback(
+ async updatedSurvey => {
+ await JobTemplatesAPI.updateSurvey(template.id, updatedSurvey);
+ setSurvey(updatedSurvey);
+ },
+ [template.id, setSurvey]
+ )
+ );
- const deleteSurvey = async () => {
- await JobTemplatesAPI.destroySurvey(template.id);
- setSurvey(null);
- };
+ const {
+ request: deleteSurvey,
+ isLoading: isDeleteLoading,
+ error: deleteError,
+ } = useRequest(
+ useCallback(async () => {
+ await JobTemplatesAPI.destroySurvey(template.id);
+ setSurvey(null);
+ }, [template.id, setSurvey])
+ );
- const toggleSurvey = async () => {
- await JobTemplatesAPI.update(template.id, {
- survey_enabled: !surveyEnabled,
- });
- setSurveyEnabled(!surveyEnabled);
- };
+ const {
+ request: toggleSurvey,
+ isLoading: isToggleLoading,
+ error: toggleError,
+ } = useRequest(
+ useCallback(async () => {
+ await JobTemplatesAPI.update(template.id, {
+ survey_enabled: !surveyEnabled,
+ });
+ setSurveyEnabled(!surveyEnabled);
+ }, [template.id, surveyEnabled])
+ );
- // TODO
- // if (contentError) {
- // return ;
+ const { error, dismissError } = useDismissableError(
+ updateError || deleteError || toggleError
+ );
+
+ if (loadingError) {
+ return ;
+ }
return (
-
-
-
-
-
+ <>
+
+
+ updateSurvey({ ...survey, spec })}
+ deleteSurvey={deleteSurvey}
+ />
+
+
+ {error && (
+
+ {i18n._(t`Failed to update survey.`)}
+
+
+ )}
+ >
);
}
+
+export default withI18n()(TemplateSurvey);
diff --git a/awx/ui_next/src/screens/Template/shared/SurveyList.jsx b/awx/ui_next/src/screens/Template/shared/SurveyList.jsx
index d13ad3a2b7..f608d4caae 100644
--- a/awx/ui_next/src/screens/Template/shared/SurveyList.jsx
+++ b/awx/ui_next/src/screens/Template/shared/SurveyList.jsx
@@ -1,22 +1,17 @@
-import React, { useEffect, useCallback, useState } from 'react';
+import React, { useState } from 'react';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
-import useRequest from '@util/useRequest';
import { Button } from '@patternfly/react-core';
import ContentError from '@components/ContentError';
import ContentLoading from '@components/ContentLoading';
import ErrorDetail from '@components/ErrorDetail';
-import { JobTemplatesAPI } from '@api';
import ContentEmpty from '@components/ContentEmpty';
import AlertModal from '@components/AlertModal';
import SurveyListItem from './SurveyListItem';
import SurveyToolbar from './SurveyToolbar';
-// survey.name
-// survey.description
-// survey.spec
function SurveyList({
survey,
surveyEnabled,
@@ -27,7 +22,6 @@ function SurveyList({
}) {
const questions = survey?.spec || [];
const [selected, setSelected] = useState([]);
- // const [showError, setShowError] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const isAllSelected =
diff --git a/awx/ui_next/src/util/useRequest.js b/awx/ui_next/src/util/useRequest.js
index 906f3160be..9f0ea0c91f 100644
--- a/awx/ui_next/src/util/useRequest.js
+++ b/awx/ui_next/src/util/useRequest.js
@@ -8,11 +8,13 @@ import {
/*
* The useRequest hook accepts a request function and returns an object with
- * four values:
+ * six values:
* request: a function to call to invoke the request
* result: the value returned from the request function (once invoked)
* isLoading: boolean state indicating whether the request is in active/in flight
* error: any caught error resulting from the request
+ * setValue: setter to explicitly set the result value
+ * isInitialized: set to true once the result is initially fetched
*
* The hook also accepts an optional second parameter which is a default
* value to set as result before the first time the request is made.
@@ -34,23 +36,42 @@ export default function useRequest(makeRequest, initialValue) {
result,
error,
isLoading,
- request: useCallback(async () => {
- setIsLoading(true);
- try {
- const response = await makeRequest();
- if (isMounted.current) {
- setResult(response);
+ request: useCallback(
+ async (...args) => {
+ setIsLoading(true);
+ try {
+ const response = await makeRequest(...args);
+ if (isMounted.current) {
+ setResult(response);
+ }
+ } catch (err) {
+ if (isMounted.current) {
+ setError(err);
+ }
+ } finally {
+ if (isMounted.current) {
+ setIsLoading(false);
+ }
}
- } catch (err) {
- if (isMounted.current) {
- setError(err);
- }
- } finally {
- if (isMounted.current) {
- setIsLoading(false);
- }
- }
- }, [makeRequest]),
+ },
+ [makeRequest]
+ ),
+ setValue: setResult,
+ };
+}
+
+export function useDismissableError(error) {
+ const [showError, setShowError] = useState(false);
+
+ useEffect(() => {
+ if (error) {
+ setShowError(true);
+ }
+ }, [error]);
+
+ return {
+ error: showError && error,
+ dismissError: () => setShowError(false),
};
}
@@ -60,14 +81,8 @@ export function useDeleteItems(
) {
const location = useLocation();
const history = useHistory();
- const [showError, setShowError] = useState(false);
- const { error, isLoading, request } = useRequest(makeRequest, null);
-
- useEffect(() => {
- if (error) {
- setShowError(true);
- }
- }, [error]);
+ const { requestError, isLoading, request } = useRequest(makeRequest, null);
+ const { error, dismissError } = useDismissableError(requestError);
const deleteItems = async () => {
await request();
@@ -91,7 +106,7 @@ export function useDeleteItems(
return {
isLoading,
deleteItems,
- deletionError: showError && error,
- clearDeletionError: () => setShowError(false),
+ deletionError: error,
+ clearDeletionError: dismissError,
};
}