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:17 UTC

[airflow] 08/11: use API

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 e9ab6119a09fcf5a7b0907376cc157dfc8c80cdd
Author: Brent Bovenzi <br...@gmail.com>
AuthorDate: Tue Mar 1 19:44:16 2022 -0500

    use API
---
 airflow/www/package.json                           |   1 +
 airflow/www/static/js/tree/StatusBox.jsx           |   6 +-
 airflow/www/static/js/tree/Tree.jsx                |  16 ++--
 .../tree/{details/content/Dag.jsx => api/index.js} |  21 +++--
 .../{details/content/Dag.jsx => api/useDag.js}     |  19 ++--
 .../{details/content/Dag.jsx => api/useTasks.js}   |  19 ++--
 airflow/www/static/js/tree/dagRuns/Bar.jsx         |   2 +-
 airflow/www/static/js/tree/details/Header.jsx      |   5 +-
 airflow/www/static/js/tree/details/content/Dag.jsx |  36 ++++++--
 .../js/tree/details/content/TaskInstance.jsx       | 100 +++++++++++----------
 airflow/www/static/js/tree/details/index.jsx       |  34 ++++---
 airflow/www/static/js/tree/index.jsx               |   8 ++
 airflow/www/yarn.lock                              |  12 +++
 13 files changed, 168 insertions(+), 111 deletions(-)

diff --git a/airflow/www/package.json b/airflow/www/package.json
index c5a4f3a..717d5b1 100644
--- a/airflow/www/package.json
+++ b/airflow/www/package.json
@@ -74,6 +74,7 @@
     "@emotion/cache": "^11.4.0",
     "@emotion/react": "^11.4.1",
     "@emotion/styled": "^11",
+    "axios": "^0.26.0",
     "bootstrap-3-typeahead": "^4.0.2",
     "camelcase-keys": "^7.0.0",
     "codemirror": "^5.59.1",
diff --git a/airflow/www/static/js/tree/StatusBox.jsx b/airflow/www/static/js/tree/StatusBox.jsx
index 9609d9a..62762c5 100644
--- a/airflow/www/static/js/tree/StatusBox.jsx
+++ b/airflow/www/static/js/tree/StatusBox.jsx
@@ -32,7 +32,7 @@ import InstanceTooltip from './InstanceTooltip';
 const StatusBox = ({
   group, instance, containerRef, onSelect, selected, ...rest
 }) => {
-  const { runId } = instance;
+  const { runId, taskId } = instance;
   const { colors } = useTheme();
   const hoverBlue = `${colors.blue[100]}50`;
 
@@ -51,7 +51,9 @@ const StatusBox = ({
   const onClick = (e) => {
     e.stopPropagation();
     onMouseLeave();
-    onSelect(instance);
+    onSelect({
+      taskId, runId, instance, task: group,
+    });
   };
 
   return (
diff --git a/airflow/www/static/js/tree/Tree.jsx b/airflow/www/static/js/tree/Tree.jsx
index 1f60d03..aeeaeb5 100644
--- a/airflow/www/static/js/tree/Tree.jsx
+++ b/airflow/www/static/js/tree/Tree.jsx
@@ -63,14 +63,14 @@ const Tree = () => {
     <Flex pl="24px" position="relative" flexDirection="row" justifyContent="space-between" ref={containerRef}>
       <Text transform="rotate(-90deg)" position="absolute" left="-6px" top="130px">Runs</Text>
       <Text transform="rotate(-90deg)" position="absolute" left="-6px" top="190px">Tasks</Text>
-      <Box mr="12px" pb="12px" overflowX="auto" ref={scrollRef} maxWidth="300px" minWidth="300px" position="relative">
-        <FormControl display="flex" alignItems="center" justifyContent="flex-end" width="100%" mb={2}>
-          {isRefreshOn && <Spinner color="blue.500" speed="1s" mr="4px" />}
-          <FormLabel htmlFor="auto-refresh" mb={0} fontSize="12px" fontWeight="normal">
-            Auto-refresh
-          </FormLabel>
-          <Switch id="auto-refresh" onChange={onToggleRefresh} isChecked={isRefreshOn} size="lg" />
-        </FormControl>
+      <FormControl display="flex" position="absolute" left="220px">
+        {isRefreshOn && <Spinner color="blue.500" speed="1s" mr="4px" />}
+        <FormLabel htmlFor="auto-refresh" mb={0} fontSize="12px" fontWeight="normal">
+          Auto-refresh
+        </FormLabel>
+        <Switch id="auto-refresh" onChange={onToggleRefresh} isChecked={isRefreshOn} size="lg" />
+      </FormControl>
+      <Box mr="12px" pb="12px" overflowX="auto" ref={scrollRef} maxWidth="300px" minWidth="300px" position="relative" mt="24px">
         <Table height={0}>
           <Thead>
             <DagRuns
diff --git a/airflow/www/static/js/tree/details/content/Dag.jsx b/airflow/www/static/js/tree/api/index.js
similarity index 72%
copy from airflow/www/static/js/tree/details/content/Dag.jsx
copy to airflow/www/static/js/tree/api/index.js
index be460ba..58edd88 100644
--- a/airflow/www/static/js/tree/details/content/Dag.jsx
+++ b/airflow/www/static/js/tree/api/index.js
@@ -17,15 +17,18 @@
  * under the License.
  */
 
-import React from 'react';
-import {
-  Text,
-} from '@chakra-ui/react';
+import axios from 'axios';
+import camelcaseKeys from 'camelcase-keys';
 
-const Dag = () => (
-  <>
-    <Text>dag details</Text>
-  </>
+import useDag from './useDag';
+import useTasks from './useTasks';
+
+axios.defaults.baseURL = '/api/v1';
+axios.interceptors.response.use(
+  (res) => (res.data ? camelcaseKeys(res.data, { deep: true }) : res),
 );
 
-export default Dag;
+export {
+  useDag,
+  useTasks,
+};
diff --git a/airflow/www/static/js/tree/details/content/Dag.jsx b/airflow/www/static/js/tree/api/useDag.js
similarity index 80%
copy from airflow/www/static/js/tree/details/content/Dag.jsx
copy to airflow/www/static/js/tree/api/useDag.js
index be460ba..6c19ee4 100644
--- a/airflow/www/static/js/tree/details/content/Dag.jsx
+++ b/airflow/www/static/js/tree/api/useDag.js
@@ -17,15 +17,12 @@
  * under the License.
  */
 
-import React from 'react';
-import {
-  Text,
-} from '@chakra-ui/react';
+import axios from 'axios';
+import { useQuery } from 'react-query';
 
-const Dag = () => (
-  <>
-    <Text>dag details</Text>
-  </>
-);
-
-export default Dag;
+export default function useDag(dagId) {
+  return useQuery(
+    ['dag', dagId],
+    () => axios.get(`/dags/${dagId}`),
+  );
+}
diff --git a/airflow/www/static/js/tree/details/content/Dag.jsx b/airflow/www/static/js/tree/api/useTasks.js
similarity index 80%
copy from airflow/www/static/js/tree/details/content/Dag.jsx
copy to airflow/www/static/js/tree/api/useTasks.js
index be460ba..3dd11e30 100644
--- a/airflow/www/static/js/tree/details/content/Dag.jsx
+++ b/airflow/www/static/js/tree/api/useTasks.js
@@ -17,15 +17,12 @@
  * under the License.
  */
 
-import React from 'react';
-import {
-  Text,
-} from '@chakra-ui/react';
+import axios from 'axios';
+import { useQuery } from 'react-query';
 
-const Dag = () => (
-  <>
-    <Text>dag details</Text>
-  </>
-);
-
-export default Dag;
+export default function useTasks(dagId) {
+  return useQuery(
+    ['tasks', dagId],
+    () => axios.get(`/dags/${dagId}/tasks`),
+  );
+}
diff --git a/airflow/www/static/js/tree/dagRuns/Bar.jsx b/airflow/www/static/js/tree/dagRuns/Bar.jsx
index 1c8dd6b..94c84e7 100644
--- a/airflow/www/static/js/tree/dagRuns/Bar.jsx
+++ b/airflow/www/static/js/tree/dagRuns/Bar.jsx
@@ -59,7 +59,7 @@ const DagRunBar = ({
         zIndex={1}
         onClick={(e) => {
           e.stopPropagation();
-          onSelect(run);
+          onSelect({ runId: run.runId, dagRun: run });
         }}
         position="relative"
         data-peer
diff --git a/airflow/www/static/js/tree/details/Header.jsx b/airflow/www/static/js/tree/details/Header.jsx
index b9d3086..c362606 100644
--- a/airflow/www/static/js/tree/details/Header.jsx
+++ b/airflow/www/static/js/tree/details/Header.jsx
@@ -27,9 +27,8 @@ import {
 } from '@chakra-ui/react';
 import { MdPlayArrow } from 'react-icons/md';
 
-import useTreeData from '../useTreeData';
 import { formatDateTime } from '../../datetime_utils';
-import getMetaValue from '../../meta_value';
+import { getMetaValue } from '../../utils';
 
 const dagId = getMetaValue('dag_id');
 
@@ -43,8 +42,8 @@ const LabelValue = ({ label, value }) => (
 const Header = ({
   selected: { taskId, runId },
   onSelect,
+  dagRuns,
 }) => {
-  const { data: { dagRuns = [] } } = useTreeData();
   const dagRun = dagRuns.find((r) => r.runId === runId);
   // console.log(dagRun);
   let runLabel = dagRun ? formatDateTime(dagRun.dataIntervalEnd) : '';
diff --git a/airflow/www/static/js/tree/details/content/Dag.jsx b/airflow/www/static/js/tree/details/content/Dag.jsx
index be460ba..e71d9c2 100644
--- a/airflow/www/static/js/tree/details/content/Dag.jsx
+++ b/airflow/www/static/js/tree/details/content/Dag.jsx
@@ -20,12 +20,38 @@
 import React from 'react';
 import {
   Text,
+  Tag,
+  Code,
+  Flex,
+  HStack,
 } from '@chakra-ui/react';
 
-const Dag = () => (
-  <>
-    <Text>dag details</Text>
-  </>
-);
+import { getMetaValue } from '../../../utils';
+import { useDag } from '../../api';
+
+const dagId = getMetaValue('dag_id');
+
+const Dag = () => {
+  const { data: dag } = useDag(dagId);
+  if (!dag) return null;
+  const {
+    description, tags, fileloc, owners,
+  } = dag;
+  return (
+    <>
+      {description && <Text>{description}</Text>}
+      <HStack>{tags.map((tag) => <Tag key={tag.name} size="lg">{tag.name}</Tag>)}</HStack>
+      <Text>
+        Relative File Location:
+        {' '}
+        <Code colorScheme="blackAlpha">{fileloc}</Code>
+      </Text>
+      <Flex>
+        <Text mr={2}>Owner:</Text>
+        {owners.map((o) => <Text key={o}>{o}</Text>)}
+      </Flex>
+    </>
+  );
+};
 
 export default Dag;
diff --git a/airflow/www/static/js/tree/details/content/TaskInstance.jsx b/airflow/www/static/js/tree/details/content/TaskInstance.jsx
index bc60737..0c0a21e 100644
--- a/airflow/www/static/js/tree/details/content/TaskInstance.jsx
+++ b/airflow/www/static/js/tree/details/content/TaskInstance.jsx
@@ -24,68 +24,70 @@ import {
   Text,
   Box,
 } from '@chakra-ui/react';
+import { finalStatesMap } from '../../../utils';
 
 import { formatDateTime, getDuration, formatDuration } from '../../../datetime_utils';
 
 const TaskInstance = ({
   instance: {
-    duration, operator, startDate, endDate, state, taskId, runId, // mappedStates,
+    duration, operator, startDate, endDate, state, taskId, runId, mappedStates,
   },
+  task,
 }) => {
-  const isGroup = false; // !!group.children;
+  const isGroup = !!task.children;
   const groupSummary = [];
-  // const mapSummary = [];
+  const mapSummary = [];
 
-  // if (isGroup) {
-  //   const numMap = finalStatesMap();
-  //   group.children.forEach((child) => {
-  //     const taskInstance = child.instances.find((ti) => ti.runId === runId);
-  //     if (taskInstance) {
-  //       const stateKey = taskInstance.state == null ? 'no_status' : taskInstance.state;
-  //       if (numMap.has(stateKey)) numMap.set(stateKey, numMap.get(stateKey) + 1);
-  //     }
-  //   });
-  //   numMap.forEach((key, val) => {
-  //     if (key > 0) {
-  //       groupSummary.push(
-  //         // eslint-disable-next-line react/no-array-index-key
-  //         <Text key={val} ml="10px">
-  //           {val}
-  //           {': '}
-  //           {key}
-  //         </Text>,
-  //       );
-  //     }
-  //   });
-  // }
+  if (isGroup) {
+    const numMap = finalStatesMap();
+    task.children.forEach((child) => {
+      const taskInstance = child.instances.find((ti) => ti.runId === runId);
+      if (taskInstance) {
+        const stateKey = taskInstance.state == null ? 'no_status' : taskInstance.state;
+        if (numMap.has(stateKey)) numMap.set(stateKey, numMap.get(stateKey) + 1);
+      }
+    });
+    numMap.forEach((key, val) => {
+      if (key > 0) {
+        groupSummary.push(
+          // eslint-disable-next-line react/no-array-index-key
+          <Text key={val} ml="10px">
+            {val}
+            {': '}
+            {key}
+          </Text>,
+        );
+      }
+    });
+  }
 
-  // if (group.isMapped && mappedStates) {
-  //   const numMap = finalStatesMap();
-  //   mappedStates.forEach((s) => {
-  //     const stateKey = s || 'no_status';
-  //     if (numMap.has(stateKey)) numMap.set(stateKey, numMap.get(stateKey) + 1);
-  //   });
-  //   numMap.forEach((key, val) => {
-  //     if (key > 0) {
-  //       mapSummary.push(
-  //         // eslint-disable-next-line react/no-array-index-key
-  //         <Text key={val} ml="10px">
-  //           {val}
-  //           {': '}
-  //           {key}
-  //         </Text>,
-  //       );
-  //     }
-  //   });
-  // }
+  if (task.isMapped && mappedStates) {
+    const numMap = finalStatesMap();
+    mappedStates.forEach((s) => {
+      const stateKey = s || 'no_status';
+      if (numMap.has(stateKey)) numMap.set(stateKey, numMap.get(stateKey) + 1);
+    });
+    numMap.forEach((key, val) => {
+      if (key > 0) {
+        mapSummary.push(
+          // eslint-disable-next-line react/no-array-index-key
+          <Text key={val} ml="10px">
+            {val}
+            {': '}
+            {key}
+          </Text>,
+        );
+      }
+    });
+  }
 
   const taskIdTitle = isGroup ? 'Task Group Id: ' : 'Task Id: ';
 
   return (
     <Box fontSize="12px" py="4px">
-      {/* {group.tooltip && (
-        <Text>{group.tooltip}</Text>
-      )} */}
+      {task.tooltip && (
+        <Text>{task.tooltip}</Text>
+      )}
       <Text>
         <Text as="strong">Status:</Text>
         {' '}
@@ -98,7 +100,7 @@ const TaskInstance = ({
           {groupSummary}
         </>
       )}
-      {/* {group.isMapped && (
+      {task.isMapped && (
         <>
           <br />
           <Text as="strong">
@@ -109,7 +111,7 @@ const TaskInstance = ({
           </Text>
           {mapSummary}
         </>
-      )} */}
+      )}
       <br />
       <Text>
         {taskIdTitle}
diff --git a/airflow/www/static/js/tree/details/index.jsx b/airflow/www/static/js/tree/details/index.jsx
index 31610a7..ee3b044 100644
--- a/airflow/www/static/js/tree/details/index.jsx
+++ b/airflow/www/static/js/tree/details/index.jsx
@@ -28,21 +28,31 @@ import Header from './Header';
 import TaskInstanceContent from './content/TaskInstance';
 import DagRunContent from './content/DagRun';
 import DagContent from './content/Dag';
+import useTreeData from '../useTreeData';
 
 const Details = ({
   selected,
   onSelect,
-}) => (
-  <Flex borderLeftWidth="1px" flexDirection="column" p={3} flexGrow={1}>
-    <Header selected={selected} onSelect={onSelect} />
-    <Divider my={2} />
-    <Box>
-      {/* TODO: get full instance data from the API */}
-      {!selected.runId && !selected.taskId && <DagContent />}
-      {selected.runId && !selected.taskId && <DagRunContent dagRun={selected} />}
-      {selected.taskId && <TaskInstanceContent instance={selected} />}
-    </Box>
-  </Flex>
-);
+}) => {
+  const { data: { dagRuns = [] } } = useTreeData();
+  console.log(selected);
+  return (
+    <Flex borderLeftWidth="1px" flexDirection="column" p={3} flexGrow={1}>
+      <Header selected={selected} onSelect={onSelect} dagRuns={dagRuns} />
+      <Divider my={2} />
+      <Box>
+        {/* TODO: get full instance data from the API */}
+        {!selected.runId && !selected.taskId && <DagContent />}
+        {selected.runId && !selected.taskId && <DagRunContent dagRun={selected.dagRun} />}
+        {selected.taskId && (
+        <TaskInstanceContent
+          instance={selected.instance}
+          task={selected.task}
+        />
+        )}
+      </Box>
+    </Flex>
+  );
+};
 
 export default Details;
diff --git a/airflow/www/static/js/tree/index.jsx b/airflow/www/static/js/tree/index.jsx
index 2c86478..a509d25 100644
--- a/airflow/www/static/js/tree/index.jsx
+++ b/airflow/www/static/js/tree/index.jsx
@@ -38,6 +38,14 @@ const mainElement = document.getElementById('react-container');
 shadowRoot.appendChild(mainElement);
 const queryClient = new QueryClient();
 
+const queryClient = new QueryClient({
+  defaultOptions: {
+    queries: {
+      refetchOnWindowFocus: false,
+    },
+  },
+});
+
 function App() {
   return (
     <React.StrictMode>
diff --git a/airflow/www/yarn.lock b/airflow/www/yarn.lock
index 26d00cb..7da2551 100644
--- a/airflow/www/yarn.lock
+++ b/airflow/www/yarn.lock
@@ -3254,6 +3254,13 @@ axe-core@^4.0.2:
   resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.3.tgz#b55cd8e8ddf659fe89b064680e1c6a4dceab0325"
   integrity sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA==
 
+axios@^0.26.0:
+  version "0.26.0"
+  resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.0.tgz#9a318f1c69ec108f8cd5f3c3d390366635e13928"
+  integrity sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==
+  dependencies:
+    follow-redirects "^1.14.8"
+
 axobject-query@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@@ -6018,6 +6025,11 @@ focus-lock@^0.9.1:
   dependencies:
     tslib "^2.0.3"
 
+follow-redirects@^1.14.8:
+  version "1.14.9"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
+  integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
+
 for-in@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"