You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by bb...@apache.org on 2022/03/08 20:57:20 UTC

[airflow] 11/11: dag run actions w/ react-query

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

bbovenzi pushed a commit to branch mapped-task-drawer
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit 194a371f11e026ba74a7a15691d63d190ea4cae8
Author: Brent Bovenzi <br...@gmail.com>
AuthorDate: Tue Mar 8 15:54:45 2022 -0500

    dag run actions w/ react-query
---
 airflow/www/static/js/tree/Tree.jsx                |  3 +-
 airflow/www/static/js/tree/api/index.js            |  6 ++
 .../js/tree/api/{index.js => useClearRun.js}       | 38 +++++++++----
 .../js/tree/api/{index.js => useMarkFailedRun.js}  | 38 +++++++++----
 .../js/tree/api/{index.js => useMarkSuccessRun.js} | 38 +++++++++----
 .../www/static/js/tree/details/content/DagRun.jsx  | 64 ++--------------------
 airflow/www/static/js/tree/index.jsx               |  1 -
 airflow/www/static/js/tree/useTreeData.js          | 20 ++++++-
 8 files changed, 110 insertions(+), 98 deletions(-)

diff --git a/airflow/www/static/js/tree/Tree.jsx b/airflow/www/static/js/tree/Tree.jsx
index b777d73..a0fa54b 100644
--- a/airflow/www/static/js/tree/Tree.jsx
+++ b/airflow/www/static/js/tree/Tree.jsx
@@ -128,7 +128,8 @@ const Tree = () => {
           overflowX="auto"
           ref={scrollRef}
           flexGrow={1}
-          width={isOpen && '300px'}
+          maxWidth={isOpen && '300px'}
+          minWidth={isOpen && '300px'}
         >
           <Table height={0}>
             <Thead>
diff --git a/airflow/www/static/js/tree/api/index.js b/airflow/www/static/js/tree/api/index.js
index 3327fef..96c4a67 100644
--- a/airflow/www/static/js/tree/api/index.js
+++ b/airflow/www/static/js/tree/api/index.js
@@ -22,6 +22,9 @@ import camelcaseKeys from 'camelcase-keys';
 
 import useDag from './useDag';
 import useTasks from './useTasks';
+import useClearRun from './useClearRun';
+import useMarkFailedRun from './useMarkFailedRun';
+import useMarkSuccessRun from './useMarkSuccessRun';
 
 axios.interceptors.response.use(
   (res) => (res.data ? camelcaseKeys(res.data, { deep: true }) : res),
@@ -30,4 +33,7 @@ axios.interceptors.response.use(
 export {
   useDag,
   useTasks,
+  useClearRun,
+  useMarkFailedRun,
+  useMarkSuccessRun,
 };
diff --git a/airflow/www/static/js/tree/api/index.js b/airflow/www/static/js/tree/api/useClearRun.js
similarity index 52%
copy from airflow/www/static/js/tree/api/index.js
copy to airflow/www/static/js/tree/api/useClearRun.js
index 3327fef..8be4d17 100644
--- a/airflow/www/static/js/tree/api/index.js
+++ b/airflow/www/static/js/tree/api/useClearRun.js
@@ -18,16 +18,32 @@
  */
 
 import axios from 'axios';
-import camelcaseKeys from 'camelcase-keys';
+import { useMutation, useQueryClient } from 'react-query';
+import { getMetaValue } from '../../utils';
 
-import useDag from './useDag';
-import useTasks from './useTasks';
+export default function useClearRun(dagId, runId) {
+  const queryClient = useQueryClient();
+  return useMutation(
+    ['dagRunClear', dagId, runId],
+    () => {
+      const csrfToken = getMetaValue('csrf_token');
+      const params = new URLSearchParams({
+        csrf_token: csrfToken,
+        confirmed: true,
+        dag_id: dagId,
+        dag_run_id: runId,
+      }).toString();
 
-axios.interceptors.response.use(
-  (res) => (res.data ? camelcaseKeys(res.data, { deep: true }) : res),
-);
-
-export {
-  useDag,
-  useTasks,
-};
+      return axios.post('/dagrun_clear', params, {
+        headers: {
+          'Content-Type': 'application/x-www-form-urlencoded',
+        },
+      });
+    },
+    {
+      onSettled: () => {
+        queryClient.invalidateQueries('treeData');
+      },
+    },
+  );
+}
diff --git a/airflow/www/static/js/tree/api/index.js b/airflow/www/static/js/tree/api/useMarkFailedRun.js
similarity index 52%
copy from airflow/www/static/js/tree/api/index.js
copy to airflow/www/static/js/tree/api/useMarkFailedRun.js
index 3327fef..dec25fe 100644
--- a/airflow/www/static/js/tree/api/index.js
+++ b/airflow/www/static/js/tree/api/useMarkFailedRun.js
@@ -18,16 +18,32 @@
  */
 
 import axios from 'axios';
-import camelcaseKeys from 'camelcase-keys';
+import { useMutation, useQueryClient } from 'react-query';
+import { getMetaValue } from '../../utils';
 
-import useDag from './useDag';
-import useTasks from './useTasks';
+export default function useMarkFailedRun(dagId, runId) {
+  const queryClient = useQueryClient();
+  return useMutation(
+    ['dagRunFailed', dagId, runId],
+    () => {
+      const csrfToken = getMetaValue('csrf_token');
+      const params = new URLSearchParams({
+        csrf_token: csrfToken,
+        confirmed: true,
+        dag_id: dagId,
+        dag_run_id: runId,
+      }).toString();
 
-axios.interceptors.response.use(
-  (res) => (res.data ? camelcaseKeys(res.data, { deep: true }) : res),
-);
-
-export {
-  useDag,
-  useTasks,
-};
+      return axios.post('/dagrun_failed', params, {
+        headers: {
+          'Content-Type': 'application/x-www-form-urlencoded',
+        },
+      });
+    },
+    {
+      onSettled: () => {
+        queryClient.invalidateQueries('treeData');
+      },
+    },
+  );
+}
diff --git a/airflow/www/static/js/tree/api/index.js b/airflow/www/static/js/tree/api/useMarkSuccessRun.js
similarity index 52%
copy from airflow/www/static/js/tree/api/index.js
copy to airflow/www/static/js/tree/api/useMarkSuccessRun.js
index 3327fef..7e1d589 100644
--- a/airflow/www/static/js/tree/api/index.js
+++ b/airflow/www/static/js/tree/api/useMarkSuccessRun.js
@@ -18,16 +18,32 @@
  */
 
 import axios from 'axios';
-import camelcaseKeys from 'camelcase-keys';
+import { useMutation, useQueryClient } from 'react-query';
+import { getMetaValue } from '../../utils';
 
-import useDag from './useDag';
-import useTasks from './useTasks';
+export default function useMarkSuccessRun(dagId, runId) {
+  const queryClient = useQueryClient();
+  return useMutation(
+    ['dagRunSuccess', dagId, runId],
+    () => {
+      const csrfToken = getMetaValue('csrf_token');
+      const params = new URLSearchParams({
+        csrf_token: csrfToken,
+        confirmed: true,
+        dag_id: dagId,
+        dag_run_id: runId,
+      }).toString();
 
-axios.interceptors.response.use(
-  (res) => (res.data ? camelcaseKeys(res.data, { deep: true }) : res),
-);
-
-export {
-  useDag,
-  useTasks,
-};
+      return axios.post('/dagrun_success', params, {
+        headers: {
+          'Content-Type': 'application/x-www-form-urlencoded',
+        },
+      });
+    },
+    {
+      onSettled: () => {
+        queryClient.invalidateQueries('treeData');
+      },
+    },
+  );
+}
diff --git a/airflow/www/static/js/tree/details/content/DagRun.jsx b/airflow/www/static/js/tree/details/content/DagRun.jsx
index be06794..b47907b 100644
--- a/airflow/www/static/js/tree/details/content/DagRun.jsx
+++ b/airflow/www/static/js/tree/details/content/DagRun.jsx
@@ -20,7 +20,6 @@
 /* global moment */
 
 import React from 'react';
-import axios from 'axios';
 import {
   Flex,
   Text,
@@ -30,71 +29,16 @@ import {
 import { MdPlayArrow } from 'react-icons/md';
 
 import { formatDateTime, formatDuration } from '../../../datetime_utils';
-import { getMetaValue } from '../../../utils';
+import { useClearRun, useMarkFailedRun, useMarkSuccessRun } from '../../api';
 
 const DagRun = ({
   dagRun: {
     dagId, state, runId, duration, dataIntervalStart, dataIntervalEnd, startDate, endDate, runType,
   },
 }) => {
-  const csrfToken = getMetaValue('csrf_token');
-
-  const onClear = async () => {
-    const params = new URLSearchParams({
-      csrf_token: csrfToken,
-      confirmed: true,
-      dag_id: dagId,
-      dag_run_id: runId,
-    }).toString();
-
-    try {
-      await axios.post('/dagrun_clear', params, {
-        headers: {
-          'Content-Type': 'application/x-www-form-urlencoded',
-        },
-      });
-    } catch (e) {
-      console.error(e);
-    }
-  };
-
-  const markFailed = async () => {
-    const params = new URLSearchParams({
-      csrf_token: csrfToken,
-      confirmed: true,
-      dag_id: dagId,
-      dag_run_id: runId,
-    }).toString();
-
-    try {
-      await axios.post('/dagrun_failed', params, {
-        headers: {
-          'Content-Type': 'application/x-www-form-urlencoded',
-        },
-      });
-    } catch (e) {
-      console.error(e);
-    }
-  };
-
-  const markSuccess = async () => {
-    const params = new URLSearchParams({
-      csrf_token: csrfToken,
-      confirmed: true,
-      dag_id: dagId,
-      dag_run_id: runId,
-    }).toString();
-
-    try {
-      await axios.post('/dagrun_success', params, {
-        headers: {
-          'Content-Type': 'application/x-www-form-urlencoded',
-        },
-      });
-    } catch (e) {
-      console.error(e);
-    }
-  };
+  const { mutate: onClear } = useClearRun(dagId, runId);
+  const { mutate: markFailed } = useMarkFailedRun(dagId, runId);
+  const { mutate: markSuccess } = useMarkSuccessRun(dagId, runId);
 
   return (
     <Box fontSize="12px" py="4px">
diff --git a/airflow/www/static/js/tree/index.jsx b/airflow/www/static/js/tree/index.jsx
index a509d25..4eea821 100644
--- a/airflow/www/static/js/tree/index.jsx
+++ b/airflow/www/static/js/tree/index.jsx
@@ -36,7 +36,6 @@ const myCache = createCache({
 });
 const mainElement = document.getElementById('react-container');
 shadowRoot.appendChild(mainElement);
-const queryClient = new QueryClient();
 
 const queryClient = new QueryClient({
   defaultOptions: {
diff --git a/airflow/www/static/js/tree/useTreeData.js b/airflow/www/static/js/tree/useTreeData.js
index e26a9c9..24481b3 100644
--- a/airflow/www/static/js/tree/useTreeData.js
+++ b/airflow/www/static/js/tree/useTreeData.js
@@ -19,6 +19,7 @@
 
 /* global treeData, localStorage, autoRefreshInterval, fetch */
 
+import { useEffect } from 'react';
 import { useDisclosure } from '@chakra-ui/react';
 import camelcaseKeys from 'camelcase-keys';
 import { useQuery } from 'react-query';
@@ -54,8 +55,9 @@ const formatData = (data) => {
 
 const useTreeData = () => {
   const initialData = formatData(treeData);
+  const canRefresh = isPaused !== 'True' && !JSON.parse(localStorage.getItem(autoRefreshKey));
 
-  const defaultIsOpen = isPaused !== 'True' && !JSON.parse(localStorage.getItem(autoRefreshKey)) && areActiveRuns(initialData.dagRuns);
+  const defaultIsOpen = canRefresh && areActiveRuns(initialData.dagRuns);
   const { isOpen: isRefreshOn, onToggle, onClose } = useDisclosure({ defaultIsOpen });
 
   const onToggleRefresh = () => {
@@ -88,12 +90,24 @@ const useTreeData = () => {
       dagRuns: [],
     };
   }, {
-    // only enabled and refetch if the refresh switch is on
-    enabled: isRefreshOn,
+    // only refetch if the refresh switch is on
     refetchInterval: isRefreshOn && autoRefreshInterval * 1000,
     initialData,
   });
 
+  // turn on autorefresh if data is active again
+  useEffect(() => {
+    if (
+      query.data.dagRuns
+      && JSON.stringify(query.data.dagRuns) !== JSON.stringify(initialData.dagRuns)
+      && canRefresh
+      && areActiveRuns(query.data.dagRuns)
+      && !isRefreshOn
+    ) {
+      onToggle();
+    }
+  }, [canRefresh, initialData.dagRuns, isRefreshOn, onToggle, query.data]);
+
   return {
     ...query,
     isRefreshOn,