You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by je...@apache.org on 2022/04/21 17:27:06 UTC

[airflow] branch main updated: Fix error handling in Grid view (#23152)

This is an automated email from the ASF dual-hosted git repository.

jedcunningham pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new c6b27e309c Fix error handling in Grid view (#23152)
c6b27e309c is described below

commit c6b27e309cbc29c73604969c88c8e8b437af583f
Author: Brent Bovenzi <br...@gmail.com>
AuthorDate: Thu Apr 21 13:26:56 2022 -0400

    Fix error handling in Grid view (#23152)
---
 airflow/www/static/js/tree/api/useClearRun.js      | 23 +++------
 airflow/www/static/js/tree/api/useClearTask.js     | 23 +++------
 airflow/www/static/js/tree/api/useMarkFailedRun.js |  3 ++
 .../www/static/js/tree/api/useMarkFailedTask.js    |  3 ++
 .../www/static/js/tree/api/useMarkSuccessRun.js    |  3 ++
 .../www/static/js/tree/api/useMarkSuccessTask.js   |  3 ++
 airflow/www/static/js/tree/api/useQueueRun.js      |  3 ++
 airflow/www/static/js/tree/api/useRunTask.js       | 23 +++------
 airflow/www/static/js/tree/api/useTreeData.js      | 15 +++---
 .../js/tree/details/content/dagRun/ClearRun.jsx    | 20 +++-----
 .../tree/details/content/dagRun/MarkFailedRun.jsx  | 10 ++--
 .../tree/details/content/dagRun/MarkSuccessRun.jsx | 20 +++-----
 .../js/tree/details/content/dagRun/QueueRun.jsx    | 20 +++-----
 .../content/taskInstance/taskActions/Clear.jsx     | 56 ++++++++++------------
 .../taskInstance/taskActions/MarkFailed.jsx        | 44 +++++++----------
 .../taskInstance/taskActions/MarkSuccess.jsx       | 44 +++++++----------
 airflow/www/static/js/tree/useErrorToast.js        | 46 ++++++++++++++++++
 airflow/www/views.py                               | 24 +++++-----
 18 files changed, 182 insertions(+), 201 deletions(-)

diff --git a/airflow/www/static/js/tree/api/useClearRun.js b/airflow/www/static/js/tree/api/useClearRun.js
index 9a5c21a8aa..afcb620c3a 100644
--- a/airflow/www/static/js/tree/api/useClearRun.js
+++ b/airflow/www/static/js/tree/api/useClearRun.js
@@ -18,17 +18,17 @@
  */
 
 import axios from 'axios';
-import { useToast } from '@chakra-ui/react';
 import { useMutation, useQueryClient } from 'react-query';
 import { getMetaValue } from '../../utils';
 import { useAutoRefresh } from '../context/autorefresh';
+import useErrorToast from '../useErrorToast';
 
 const csrfToken = getMetaValue('csrf_token');
 const clearRunUrl = getMetaValue('dagrun_clear_url');
 
 export default function useClearRun(dagId, runId) {
   const queryClient = useQueryClient();
-  const toast = useToast();
+  const errorToast = useErrorToast();
   const { startRefresh } = useAutoRefresh();
   return useMutation(
     ['dagRunClear', dagId, runId],
@@ -47,21 +47,12 @@ export default function useClearRun(dagId, runId) {
       });
     },
     {
-      onSuccess: (data) => {
-        const { message, status } = data;
-        if (message && status === 'error') {
-          toast({
-            description: message,
-            isClosable: true,
-            status,
-          });
-        }
-        if (!status || status !== 'error') {
-          // Invalidating the query will force a new API request
-          queryClient.invalidateQueries('treeData');
-          startRefresh();
-        }
+      onSuccess: () => {
+        // Invalidating the query will force a new API request
+        queryClient.invalidateQueries('treeData');
+        startRefresh();
       },
+      onError: (error) => errorToast({ error }),
     },
   );
 }
diff --git a/airflow/www/static/js/tree/api/useClearTask.js b/airflow/www/static/js/tree/api/useClearTask.js
index 2ea3eee486..777a621aaf 100644
--- a/airflow/www/static/js/tree/api/useClearTask.js
+++ b/airflow/www/static/js/tree/api/useClearTask.js
@@ -18,10 +18,10 @@
  */
 
 import axios from 'axios';
-import { useToast } from '@chakra-ui/react';
 import { useMutation, useQueryClient } from 'react-query';
 import { getMetaValue } from '../../utils';
 import { useAutoRefresh } from '../context/autorefresh';
+import useErrorToast from '../useErrorToast';
 
 const csrfToken = getMetaValue('csrf_token');
 const clearUrl = getMetaValue('clear_url');
@@ -30,7 +30,7 @@ export default function useClearTask({
   dagId, runId, taskId, executionDate,
 }) {
   const queryClient = useQueryClient();
-  const toast = useToast();
+  const errorToast = useErrorToast();
   const { startRefresh } = useAutoRefresh();
 
   return useMutation(
@@ -64,21 +64,12 @@ export default function useClearTask({
       });
     },
     {
-      onSuccess: (data) => {
-        const { message, status } = data;
-        if (message && status === 'error') {
-          toast({
-            description: message,
-            isClosable: true,
-            status,
-          });
-        }
-        if (!status || status !== 'error') {
-          queryClient.invalidateQueries('treeData');
-          queryClient.invalidateQueries('mappedInstances', dagId, runId, taskId);
-          startRefresh();
-        }
+      onSuccess: () => {
+        queryClient.invalidateQueries('treeData');
+        queryClient.invalidateQueries('mappedInstances', dagId, runId, taskId);
+        startRefresh();
       },
+      onError: (error) => errorToast({ error }),
     },
   );
 }
diff --git a/airflow/www/static/js/tree/api/useMarkFailedRun.js b/airflow/www/static/js/tree/api/useMarkFailedRun.js
index 725d9907db..c7487be5e9 100644
--- a/airflow/www/static/js/tree/api/useMarkFailedRun.js
+++ b/airflow/www/static/js/tree/api/useMarkFailedRun.js
@@ -21,12 +21,14 @@ import axios from 'axios';
 import { useMutation, useQueryClient } from 'react-query';
 import { getMetaValue } from '../../utils';
 import { useAutoRefresh } from '../context/autorefresh';
+import useErrorToast from '../useErrorToast';
 
 const csrfToken = getMetaValue('csrf_token');
 const markFailedUrl = getMetaValue('dagrun_failed_url');
 
 export default function useMarkFailedRun(dagId, runId) {
   const queryClient = useQueryClient();
+  const errorToast = useErrorToast();
   const { startRefresh } = useAutoRefresh();
   return useMutation(
     ['dagRunFailed', dagId, runId],
@@ -49,6 +51,7 @@ export default function useMarkFailedRun(dagId, runId) {
         queryClient.invalidateQueries('treeData');
         startRefresh();
       },
+      onError: (error) => errorToast({ error }),
     },
   );
 }
diff --git a/airflow/www/static/js/tree/api/useMarkFailedTask.js b/airflow/www/static/js/tree/api/useMarkFailedTask.js
index a94ab22d0c..4ef72df1ba 100644
--- a/airflow/www/static/js/tree/api/useMarkFailedTask.js
+++ b/airflow/www/static/js/tree/api/useMarkFailedTask.js
@@ -21,6 +21,7 @@ import axios from 'axios';
 import { useMutation, useQueryClient } from 'react-query';
 import { getMetaValue } from '../../utils';
 import { useAutoRefresh } from '../context/autorefresh';
+import useErrorToast from '../useErrorToast';
 
 const failedUrl = getMetaValue('failed_url');
 const csrfToken = getMetaValue('csrf_token');
@@ -29,6 +30,7 @@ export default function useMarkFailedTask({
   dagId, runId, taskId,
 }) {
   const queryClient = useQueryClient();
+  const errorToast = useErrorToast();
   const { startRefresh } = useAutoRefresh();
   return useMutation(
     ['markFailed', dagId, runId, taskId],
@@ -63,6 +65,7 @@ export default function useMarkFailedTask({
         queryClient.invalidateQueries('mappedInstances', dagId, runId, taskId);
         startRefresh();
       },
+      onError: (error) => errorToast({ error }),
     },
   );
 }
diff --git a/airflow/www/static/js/tree/api/useMarkSuccessRun.js b/airflow/www/static/js/tree/api/useMarkSuccessRun.js
index 9f769c997c..6076e6ba1e 100644
--- a/airflow/www/static/js/tree/api/useMarkSuccessRun.js
+++ b/airflow/www/static/js/tree/api/useMarkSuccessRun.js
@@ -21,12 +21,14 @@ import axios from 'axios';
 import { useMutation, useQueryClient } from 'react-query';
 import { getMetaValue } from '../../utils';
 import { useAutoRefresh } from '../context/autorefresh';
+import useErrorToast from '../useErrorToast';
 
 const markSuccessUrl = getMetaValue('dagrun_success_url');
 const csrfToken = getMetaValue('csrf_token');
 
 export default function useMarkSuccessRun(dagId, runId) {
   const queryClient = useQueryClient();
+  const errorToast = useErrorToast();
   const { startRefresh } = useAutoRefresh();
   return useMutation(
     ['dagRunSuccess', dagId, runId],
@@ -49,6 +51,7 @@ export default function useMarkSuccessRun(dagId, runId) {
         queryClient.invalidateQueries('treeData');
         startRefresh();
       },
+      onError: (error) => errorToast({ error }),
     },
   );
 }
diff --git a/airflow/www/static/js/tree/api/useMarkSuccessTask.js b/airflow/www/static/js/tree/api/useMarkSuccessTask.js
index 47fda2f0f8..14d83e653d 100644
--- a/airflow/www/static/js/tree/api/useMarkSuccessTask.js
+++ b/airflow/www/static/js/tree/api/useMarkSuccessTask.js
@@ -21,6 +21,7 @@ import axios from 'axios';
 import { useMutation, useQueryClient } from 'react-query';
 import { getMetaValue } from '../../utils';
 import { useAutoRefresh } from '../context/autorefresh';
+import useErrorToast from '../useErrorToast';
 
 const csrfToken = getMetaValue('csrf_token');
 const successUrl = getMetaValue('success_url');
@@ -29,6 +30,7 @@ export default function useMarkSuccessTask({
   dagId, runId, taskId,
 }) {
   const queryClient = useQueryClient();
+  const errorToast = useErrorToast();
   const { startRefresh } = useAutoRefresh();
   return useMutation(
     ['markSuccess', dagId, runId, taskId],
@@ -64,6 +66,7 @@ export default function useMarkSuccessTask({
         queryClient.invalidateQueries('mappedInstances', dagId, runId, taskId);
         startRefresh();
       },
+      onError: (error) => errorToast({ error }),
     },
   );
 }
diff --git a/airflow/www/static/js/tree/api/useQueueRun.js b/airflow/www/static/js/tree/api/useQueueRun.js
index 20612f1142..5848d680f9 100644
--- a/airflow/www/static/js/tree/api/useQueueRun.js
+++ b/airflow/www/static/js/tree/api/useQueueRun.js
@@ -21,12 +21,14 @@ import axios from 'axios';
 import { useMutation, useQueryClient } from 'react-query';
 import { getMetaValue } from '../../utils';
 import { useAutoRefresh } from '../context/autorefresh';
+import useErrorToast from '../useErrorToast';
 
 const csrfToken = getMetaValue('csrf_token');
 const queuedUrl = getMetaValue('dagrun_queued_url');
 
 export default function useQueueRun(dagId, runId) {
   const queryClient = useQueryClient();
+  const errorToast = useErrorToast();
   const { startRefresh } = useAutoRefresh();
   return useMutation(
     ['dagRunQueue', dagId, runId],
@@ -49,6 +51,7 @@ export default function useQueueRun(dagId, runId) {
         queryClient.invalidateQueries('treeData');
         startRefresh();
       },
+      onError: (error) => errorToast({ error }),
     },
   );
 }
diff --git a/airflow/www/static/js/tree/api/useRunTask.js b/airflow/www/static/js/tree/api/useRunTask.js
index 9e45c42f59..810b2dceab 100644
--- a/airflow/www/static/js/tree/api/useRunTask.js
+++ b/airflow/www/static/js/tree/api/useRunTask.js
@@ -18,17 +18,17 @@
  */
 
 import axios from 'axios';
-import { useToast } from '@chakra-ui/react';
 import { useMutation, useQueryClient } from 'react-query';
 import { getMetaValue } from '../../utils';
 import { useAutoRefresh } from '../context/autorefresh';
+import useErrorToast from '../useErrorToast';
 
 const csrfToken = getMetaValue('csrf_token');
 const runUrl = getMetaValue('run_url');
 
 export default function useRunTask(dagId, runId, taskId) {
   const queryClient = useQueryClient();
-  const toast = useToast();
+  const errorToast = useErrorToast();
   const { startRefresh } = useAutoRefresh();
   return useMutation(
     ['runTask', dagId, runId, taskId],
@@ -58,21 +58,12 @@ export default function useRunTask(dagId, runId, taskId) {
       }),
     ),
     {
-      onSuccess: (data) => {
-        const { message, status } = data.length ? data[0] : data;
-        if (message && status === 'error') {
-          toast({
-            description: message,
-            isClosable: true,
-            status,
-          });
-        }
-        if (!status || status !== 'error') {
-          queryClient.invalidateQueries('treeData');
-          queryClient.invalidateQueries('mappedInstances', dagId, runId, taskId);
-          startRefresh();
-        }
+      onSuccess: () => {
+        queryClient.invalidateQueries('treeData');
+        queryClient.invalidateQueries('mappedInstances', dagId, runId, taskId);
+        startRefresh();
       },
+      onError: (error) => errorToast({ error }),
     },
   );
 }
diff --git a/airflow/www/static/js/tree/api/useTreeData.js b/airflow/www/static/js/tree/api/useTreeData.js
index ddb0a9dfed..a33e17c1c4 100644
--- a/airflow/www/static/js/tree/api/useTreeData.js
+++ b/airflow/www/static/js/tree/api/useTreeData.js
@@ -21,11 +21,11 @@
 
 import { useQuery } from 'react-query';
 import axios from 'axios';
-import { useToast } from '@chakra-ui/react';
 
 import { getMetaValue } from '../../utils';
 import { useAutoRefresh } from '../context/autorefresh';
 import { formatData, areActiveRuns } from '../treeDataUtils';
+import useErrorToast from '../useErrorToast';
 
 // dagId comes from dag.html
 const dagId = getMetaValue('dag_id');
@@ -41,7 +41,7 @@ const useTreeData = () => {
   };
   const initialData = formatData(treeData, emptyData);
   const { isRefreshOn, stopRefresh } = useAutoRefresh();
-  const toast = useToast();
+  const errorToast = useErrorToast();
   return useQuery('treeData', async () => {
     try {
       const root = urlRoot ? `&root=${urlRoot}` : '';
@@ -50,16 +50,13 @@ const useTreeData = () => {
       // turn off auto refresh if there are no active runs
       if (!areActiveRuns(newData.dagRuns)) stopRefresh();
       return newData;
-    } catch (e) {
+    } catch (error) {
       stopRefresh();
-      // Display error in a toast message
-      toast({
+      errorToast({
         title: 'Auto-refresh Error',
-        description: e.message,
-        isClosable: true,
-        status: 'error',
+        error,
       });
-      throw (e);
+      throw (error);
     }
   }, {
     // only enabled and refetch if the refresh switch is on
diff --git a/airflow/www/static/js/tree/details/content/dagRun/ClearRun.jsx b/airflow/www/static/js/tree/details/content/dagRun/ClearRun.jsx
index bbd7ffba42..95aed65f8c 100644
--- a/airflow/www/static/js/tree/details/content/dagRun/ClearRun.jsx
+++ b/airflow/www/static/js/tree/details/content/dagRun/ClearRun.jsx
@@ -29,23 +29,15 @@ const ClearRun = ({ dagId, runId }) => {
   const { mutateAsync: onClear, isLoading } = useClearRun(dagId, runId);
 
   const onClick = async () => {
-    try {
-      const data = await onClear({ confirmed: false });
-      setAffectedTasks(data);
-      onOpen();
-    } catch (e) {
-      console.error(e);
-    }
+    const data = await onClear({ confirmed: false });
+    setAffectedTasks(data);
+    onOpen();
   };
 
   const onConfirm = async () => {
-    try {
-      await onClear({ confirmed: true });
-      setAffectedTasks([]);
-      onClose();
-    } catch (e) {
-      console.error(e);
-    }
+    await onClear({ confirmed: true });
+    setAffectedTasks([]);
+    onClose();
   };
 
   return (
diff --git a/airflow/www/static/js/tree/details/content/dagRun/MarkFailedRun.jsx b/airflow/www/static/js/tree/details/content/dagRun/MarkFailedRun.jsx
index 226643a77b..483dbd4fe0 100644
--- a/airflow/www/static/js/tree/details/content/dagRun/MarkFailedRun.jsx
+++ b/airflow/www/static/js/tree/details/content/dagRun/MarkFailedRun.jsx
@@ -29,13 +29,9 @@ const MarkFailedRun = ({ dagId, runId }) => {
   const { mutateAsync: markFailed, isLoading } = useMarkFailedRun(dagId, runId);
 
   const onClick = async () => {
-    try {
-      const data = await markFailed({ confirmed: false });
-      setAffectedTasks(data);
-      onOpen();
-    } catch (error) {
-      console.error(error);
-    }
+    const data = await markFailed({ confirmed: false });
+    setAffectedTasks(data);
+    onOpen();
   };
 
   const onConfirm = () => {
diff --git a/airflow/www/static/js/tree/details/content/dagRun/MarkSuccessRun.jsx b/airflow/www/static/js/tree/details/content/dagRun/MarkSuccessRun.jsx
index 30715c340b..0d1d8483d3 100644
--- a/airflow/www/static/js/tree/details/content/dagRun/MarkSuccessRun.jsx
+++ b/airflow/www/static/js/tree/details/content/dagRun/MarkSuccessRun.jsx
@@ -29,23 +29,15 @@ const MarkSuccessRun = ({ dagId, runId }) => {
   const { mutateAsync: markSuccess, isLoading } = useMarkSuccessRun(dagId, runId);
 
   const onClick = async () => {
-    try {
-      const data = await markSuccess({ confirmed: false });
-      setAffectedTasks(data);
-      onOpen();
-    } catch (e) {
-      console.error(e);
-    }
+    const data = await markSuccess({ confirmed: false });
+    setAffectedTasks(data);
+    onOpen();
   };
 
   const onConfirm = async () => {
-    try {
-      await markSuccess({ confirmed: true });
-      setAffectedTasks([]);
-      onClose();
-    } catch (e) {
-      console.error(e);
-    }
+    await markSuccess({ confirmed: true });
+    setAffectedTasks([]);
+    onClose();
   };
 
   return (
diff --git a/airflow/www/static/js/tree/details/content/dagRun/QueueRun.jsx b/airflow/www/static/js/tree/details/content/dagRun/QueueRun.jsx
index ef87d9fea5..ceec45ffdd 100644
--- a/airflow/www/static/js/tree/details/content/dagRun/QueueRun.jsx
+++ b/airflow/www/static/js/tree/details/content/dagRun/QueueRun.jsx
@@ -30,24 +30,16 @@ const QueueRun = ({ dagId, runId }) => {
 
   // Get what the changes will be and show it in a modal
   const onClick = async () => {
-    try {
-      const data = await onQueue({ confirmed: false });
-      setAffectedTasks(data);
-      onOpen();
-    } catch (e) {
-      console.error(e);
-    }
+    const data = await onQueue({ confirmed: false });
+    setAffectedTasks(data);
+    onOpen();
   };
 
   // Confirm changes
   const onConfirm = async () => {
-    try {
-      await onQueue({ confirmed: true });
-      setAffectedTasks([]);
-      onClose();
-    } catch (e) {
-      console.error(e);
-    }
+    await onQueue({ confirmed: true });
+    setAffectedTasks([]);
+    onClose();
   };
 
   return (
diff --git a/airflow/www/static/js/tree/details/content/taskInstance/taskActions/Clear.jsx b/airflow/www/static/js/tree/details/content/taskInstance/taskActions/Clear.jsx
index d825976ed2..de972c3f2f 100644
--- a/airflow/www/static/js/tree/details/content/taskInstance/taskActions/Clear.jsx
+++ b/airflow/www/static/js/tree/details/content/taskInstance/taskActions/Clear.jsx
@@ -65,41 +65,33 @@ const Run = ({
   });
 
   const onClick = async () => {
-    try {
-      const data = await clearTask({
-        past,
-        future,
-        upstream,
-        downstream,
-        recursive,
-        failed,
-        confirmed: false,
-        mapIndexes,
-      });
-      setAffectedTasks(data);
-      onOpen();
-    } catch (e) {
-      console.error(e);
-    }
+    const data = await clearTask({
+      past,
+      future,
+      upstream,
+      downstream,
+      recursive,
+      failed,
+      confirmed: false,
+      mapIndexes,
+    });
+    setAffectedTasks(data);
+    onOpen();
   };
 
   const onConfirm = async () => {
-    try {
-      await clearTask({
-        past,
-        future,
-        upstream,
-        downstream,
-        recursive,
-        failed,
-        confirmed: true,
-        mapIndexes,
-      });
-      setAffectedTasks([]);
-      onClose();
-    } catch (e) {
-      console.error(e);
-    }
+    await clearTask({
+      past,
+      future,
+      upstream,
+      downstream,
+      recursive,
+      failed,
+      confirmed: true,
+      mapIndexes,
+    });
+    setAffectedTasks([]);
+    onClose();
   };
 
   return (
diff --git a/airflow/www/static/js/tree/details/content/taskInstance/taskActions/MarkFailed.jsx b/airflow/www/static/js/tree/details/content/taskInstance/taskActions/MarkFailed.jsx
index 12f8bcfeef..fd75d9664d 100644
--- a/airflow/www/static/js/tree/details/content/taskInstance/taskActions/MarkFailed.jsx
+++ b/airflow/www/static/js/tree/details/content/taskInstance/taskActions/MarkFailed.jsx
@@ -63,35 +63,27 @@ const MarkFailed = ({
   });
 
   const onClick = async () => {
-    try {
-      const data = await confirmChangeMutation({
-        past,
-        future,
-        upstream,
-        downstream,
-        mapIndexes,
-      });
-      setAffectedTasks(data);
-      onOpen();
-    } catch (e) {
-      console.error(e);
-    }
+    const data = await confirmChangeMutation({
+      past,
+      future,
+      upstream,
+      downstream,
+      mapIndexes,
+    });
+    setAffectedTasks(data);
+    onOpen();
   };
 
   const onConfirm = async () => {
-    try {
-      await markFailedMutation({
-        past,
-        future,
-        upstream,
-        downstream,
-        mapIndexes,
-      });
-      setAffectedTasks([]);
-      onClose();
-    } catch (e) {
-      console.error(e);
-    }
+    await markFailedMutation({
+      past,
+      future,
+      upstream,
+      downstream,
+      mapIndexes,
+    });
+    setAffectedTasks([]);
+    onClose();
   };
 
   return (
diff --git a/airflow/www/static/js/tree/details/content/taskInstance/taskActions/MarkSuccess.jsx b/airflow/www/static/js/tree/details/content/taskInstance/taskActions/MarkSuccess.jsx
index bdf59e6a4d..515093f38d 100644
--- a/airflow/www/static/js/tree/details/content/taskInstance/taskActions/MarkSuccess.jsx
+++ b/airflow/www/static/js/tree/details/content/taskInstance/taskActions/MarkSuccess.jsx
@@ -58,35 +58,27 @@ const MarkSuccess = ({
   });
 
   const onClick = async () => {
-    try {
-      const data = await confirmChangeMutation({
-        past,
-        future,
-        upstream,
-        downstream,
-        mapIndexes,
-      });
-      setAffectedTasks(data);
-      onOpen();
-    } catch (e) {
-      console.error(e);
-    }
+    const data = await confirmChangeMutation({
+      past,
+      future,
+      upstream,
+      downstream,
+      mapIndexes,
+    });
+    setAffectedTasks(data);
+    onOpen();
   };
 
   const onConfirm = async () => {
-    try {
-      await markSuccessMutation({
-        past,
-        future,
-        upstream,
-        downstream,
-        mapIndexes,
-      });
-      setAffectedTasks([]);
-      onClose();
-    } catch (e) {
-      console.error(e);
-    }
+    await markSuccessMutation({
+      past,
+      future,
+      upstream,
+      downstream,
+      mapIndexes,
+    });
+    setAffectedTasks([]);
+    onClose();
   };
 
   return (
diff --git a/airflow/www/static/js/tree/useErrorToast.js b/airflow/www/static/js/tree/useErrorToast.js
new file mode 100644
index 0000000000..02d3195f6c
--- /dev/null
+++ b/airflow/www/static/js/tree/useErrorToast.js
@@ -0,0 +1,46 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { useToast } from '@chakra-ui/react';
+
+const getErrorDescription = (error, fallbackMessage) => {
+  if (error.response && error.response.data) {
+    return error.response.data;
+  }
+  if (error instanceof Error) return error.message;
+  if (typeof error === 'string') return error;
+  return fallbackMessage || 'Something went wrong.';
+};
+
+const getErrorTitle = (error) => (error.message || 'Error');
+
+const useErrorToast = () => {
+  const toast = useToast();
+  // Add an error prop and handle it as a description
+  return ({ error, ...rest }) => {
+    toast({
+      status: 'error',
+      title: getErrorTitle(error),
+      description: getErrorDescription(error).slice(0, 500),
+      ...rest,
+    });
+  };
+};
+
+export default useErrorToast;
diff --git a/airflow/www/views.py b/airflow/www/views.py
index ae0186e493..6113084876 100644
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -513,13 +513,15 @@ def get_task_stats_from_query(qry):
     return data
 
 
-def redirect_or_json(origin, msg, status=""):
+def redirect_or_json(origin, msg, status="", status_code=200):
     """
     Some endpoints are called by javascript,
     returning json will allow us to more elegantly handle side-effects in-page
     """
     if request.headers.get('Accept') == 'application/json':
-        return {'status': status, 'message': msg}
+        if status == 'error' and status_code == 200:
+            status_code = 500
+        return Response(response=msg, status=status_code, mimetype="application/json")
     else:
         if status:
             flash(msg, status)
@@ -1757,13 +1759,13 @@ class Airflow(AirflowBaseView):
 
         if not getattr(executor, "supports_ad_hoc_ti_run", False):
             msg = "Only works with the Celery, CeleryKubernetes or Kubernetes executors"
-            return redirect_or_json(origin, msg, "error")
+            return redirect_or_json(origin, msg, "error", 400)
 
         dag_run = dag.get_dagrun(run_id=dag_run_id)
         ti = dag_run.get_task_instance(task_id=task.task_id, map_index=map_index)
         if not ti:
             msg = "Could not queue task instance for execution, task instance is missing"
-            return redirect_or_json(origin, msg, "error")
+            return redirect_or_json(origin, msg, "error", 400)
 
         ti.refresh_from_task(task)
 
@@ -1778,7 +1780,7 @@ class Airflow(AirflowBaseView):
         if failed_deps:
             failed_deps_str = ", ".join(f"{dep.dep_name}: {dep.reason}" for dep in failed_deps)
             msg = f"Could not queue task instance for execution, dependencies not met: {failed_deps_str}"
-            return redirect_or_json(origin, msg, "error")
+            return redirect_or_json(origin, msg, "error", 400)
 
         executor.job_id = "manual"
         executor.start()
@@ -1993,13 +1995,13 @@ class Airflow(AirflowBaseView):
                 dry_run=True,
             )
         except AirflowException as ex:
-            return redirect_or_json(origin, msg=str(ex), status="error")
+            return redirect_or_json(origin, msg=str(ex), status="error", status_code=500)
 
         assert isinstance(tis, collections.abc.Iterable)
         details = [str(t) for t in tis]
 
         if not details:
-            return redirect_or_json(origin, "No task instances to clear", status="error")
+            return redirect_or_json(origin, "No task instances to clear", status="error", status_code=404)
         elif request.headers.get('Accept') == 'application/json':
             return htmlsafe_json_dumps(details, separators=(',', ':'))
         return self.render_template(
@@ -2353,13 +2355,13 @@ class Airflow(AirflowBaseView):
         dag = current_app.dag_bag.get_dag(dag_id)
         if not dag:
             msg = f'DAG {dag_id} not found'
-            return redirect_or_json(origin, msg, status='error')
+            return redirect_or_json(origin, msg, status='error', status_code=404)
 
         try:
             task = dag.get_task(task_id)
         except airflow.exceptions.TaskNotFound:
             msg = f"Task {task_id} not found"
-            return redirect_or_json(origin, msg, status='error')
+            return redirect_or_json(origin, msg, status='error', status_code=404)
 
         task.dag = dag
 
@@ -2368,12 +2370,12 @@ class Airflow(AirflowBaseView):
             'failed',
         ):
             msg = f"Invalid state {state}, must be either 'success' or 'failed'"
-            return redirect_or_json(origin, msg, status='error')
+            return redirect_or_json(origin, msg, status='error', status_code=400)
 
         latest_execution_date = dag.get_latest_execution_date()
         if not latest_execution_date:
             msg = f"Cannot mark tasks as {state}, seem that dag {dag_id} has never run"
-            return redirect_or_json(origin, msg, status='error')
+            return redirect_or_json(origin, msg, status='error', status_code=400)
 
         if map_indexes is None:
             tasks: Union[List[Operator], List[Tuple[Operator, int]]] = [task]