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/02/25 16:59:17 UTC

[airflow] 04/05: basic slide drawer

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 d98af9addcf52077cdf4d94cbd41349c29e6bdfd
Author: Brent Bovenzi <br...@gmail.com>
AuthorDate: Wed Feb 16 13:57:33 2022 -0500

    basic slide drawer
---
 airflow/www/static/js/tree/SidePanel.jsx      | 49 +++++++++++++++++
 airflow/www/static/js/tree/StatusBox.jsx      | 75 +++++++++++++++------------
 airflow/www/static/js/tree/Tree.jsx           | 25 ++++++---
 airflow/www/static/js/tree/renderTaskRows.jsx | 19 +++++--
 4 files changed, 125 insertions(+), 43 deletions(-)

diff --git a/airflow/www/static/js/tree/SidePanel.jsx b/airflow/www/static/js/tree/SidePanel.jsx
new file mode 100644
index 0000000..3515f1c
--- /dev/null
+++ b/airflow/www/static/js/tree/SidePanel.jsx
@@ -0,0 +1,49 @@
+/*!
+ * 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.
+ */
+
+/* global moment */
+
+import React from 'react';
+import {
+  Box,
+  chakra,
+  Flex,
+  Text,
+} from '@chakra-ui/react';
+
+import { formatDateTime } from '../datetime_utils';
+
+const SidePanel = ({ instance: { runId, taskId, executionDate }, isOpen }) => (
+  <Box bg="gray.200" maxWidth={isOpen ? 300 : 0} minWidth={isOpen ? 300 : 0} transition="all 0.5s" position="relative" overflow="hidden">
+    <Flex right={isOpen ? 0 : -300} top={0} transition="right 0.5s, max-width 0.5s" width={300} flexDirection="column" m={2}>
+      <Text as="h4">
+        <chakra.span>Task Instance: </chakra.span>
+        <chakra.b>{taskId}</chakra.b>
+        <chakra.span> at </chakra.span>
+        <chakra.b>{formatDateTime(moment.utc(executionDate))}</chakra.b>
+      </Text>
+      <Text>
+        {/* eslint-disable-next-line max-len */}
+        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Nunc vel risus commodo viverra maecenas accumsan. Ut porttitor leo a diam sollicitudin tempor id eu. Molestie at elementum eu facilisis sed odio morbi quis commodo. Facilisis leo vel fringilla est ullamcorper eget nulla facilisi etiam. Est sit amet facilisis magna etiam tempor orci eu. Id semper risus in hendrerit gravida rutrum. Ac odio tempor orci dapibus  [...]
+      </Text>
+    </Flex>
+  </Box>
+);
+
+export default SidePanel;
diff --git a/airflow/www/static/js/tree/StatusBox.jsx b/airflow/www/static/js/tree/StatusBox.jsx
index c22f0b2..dce0fda 100644
--- a/airflow/www/static/js/tree/StatusBox.jsx
+++ b/airflow/www/static/js/tree/StatusBox.jsx
@@ -30,12 +30,21 @@ import { callModal } from '../dag';
 import InstanceTooltip from './InstanceTooltip';
 
 const StatusBox = ({
-  group, instance, containerRef, extraLinks = [], ...rest
+  group, instance, containerRef, extraLinks = [], onSelectInstance, ...rest
 }) => {
   const {
     executionDate, taskId, tryNumber = 0, operator, runId,
   } = instance;
-  const onClick = () => executionDate && callModal(taskId, executionDate, extraLinks, tryNumber, operator === 'SubDagOperator' || undefined, runId);
+
+  const onOpenModal = () => executionDate && callModal(taskId, executionDate, extraLinks, tryNumber, operator === 'SubDagOperator' || undefined, runId);
+  const onClick = () => {
+    if (group.isMapped) {
+      onSelectInstance(instance);
+    } else {
+      onSelectInstance({});
+      onOpenModal();
+    }
+  };
 
   // Fetch the corresponding column element and set its background color when hovering
   const onMouseOver = () => {
@@ -48,37 +57,39 @@ const StatusBox = ({
   };
 
   return (
-    <Tooltip
-      label={<InstanceTooltip instance={instance} group={group} />}
-      fontSize="md"
-      portalProps={{ containerRef }}
-      hasArrow
-      placement="top"
-      openDelay={400}
-    >
-      <Flex
-        p="1px"
-        my="1px"
-        mx="2px"
-        justifyContent="center"
-        alignItems="center"
-        onClick={onClick}
-        cursor={!group.children && 'pointer'}
-        data-testid="task-instance"
-        zIndex={1}
-        onMouseEnter={onMouseOver}
-        onMouseLeave={onMouseLeave}
-        {...rest}
+    <>
+      <Tooltip
+        label={<InstanceTooltip instance={instance} group={group} />}
+        fontSize="md"
+        portalProps={{ containerRef }}
+        hasArrow
+        placement="top"
+        openDelay={400}
       >
-        <Box
-          width="10px"
-          height="10px"
-          backgroundColor={stateColors[instance.state] || 'white'}
-          borderRadius="2px"
-          borderWidth={instance.state ? 0 : 1}
-        />
-      </Flex>
-    </Tooltip>
+        <Flex
+          p="1px"
+          my="1px"
+          mx="2px"
+          justifyContent="center"
+          alignItems="center"
+          onClick={onClick}
+          cursor={!group.children && 'pointer'}
+          data-testid="task-instance"
+          zIndex={1}
+          onMouseEnter={onMouseOver}
+          onMouseLeave={onMouseLeave}
+          {...rest}
+        >
+          <Box
+            width="10px"
+            height="10px"
+            backgroundColor={stateColors[instance.state] || 'white'}
+            borderRadius="2px"
+            borderWidth={instance.state ? 0 : 1}
+          />
+        </Flex>
+      </Tooltip>
+    </>
   );
 };
 
diff --git a/airflow/www/static/js/tree/Tree.jsx b/airflow/www/static/js/tree/Tree.jsx
index 450e002..095adbd 100644
--- a/airflow/www/static/js/tree/Tree.jsx
+++ b/airflow/www/static/js/tree/Tree.jsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import React, { useRef, useEffect } from 'react';
+import React, { useRef, useEffect, useState } from 'react';
 import {
   Table,
   Tbody,
@@ -28,16 +28,19 @@ import {
   Spinner,
   Text,
   Thead,
+  Flex,
 } from '@chakra-ui/react';
 
 import useTreeData from './useTreeData';
 import renderTaskRows from './renderTaskRows';
 import DagRuns from './dagRuns';
+import SidePanel from './SidePanel';
 
 const Tree = () => {
   const containerRef = useRef();
   const scrollRef = useRef();
   const { data: { groups = {} }, isRefreshOn, onToggleRefresh } = useTreeData();
+  const [selectedInstance, setSelectedInstance] = useState({});
 
   useEffect(() => {
     // Set initial scroll to far right if it is scrollable
@@ -47,6 +50,13 @@ const Tree = () => {
     }
   }, []);
 
+  const { runId, taskId } = selectedInstance;
+  const onSelectInstance = (newInstance) => (
+    (newInstance.runId === runId && newInstance.taskId === taskId)
+      ? setSelectedInstance({})
+      : setSelectedInstance(newInstance)
+  );
+
   return (
     <Box position="relative" ref={containerRef}>
       <FormControl display="flex" alignItems="center" justifyContent="flex-end" width="100%">
@@ -58,17 +68,20 @@ const Tree = () => {
       </FormControl>
       <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 px="24px">
-        <Box position="relative" width="100%" overflowX="auto" ref={scrollRef}>
-          <Table>
+      <Box pl="24px">
+        <Flex position="relative" flexDirection="row" justifyContent="space-between" overflow="hidden">
+          <Table mr="24px" overflowX="auto" ref={scrollRef} height={0}>
             <Thead>
               <DagRuns containerRef={containerRef} />
             </Thead>
             <Tbody>
-              {renderTaskRows({ task: groups, containerRef })}
+              {renderTaskRows({
+                task: groups, containerRef, onSelectInstance,
+              })}
             </Tbody>
           </Table>
-        </Box>
+          <SidePanel isOpen={!!runId} instance={selectedInstance} />
+        </Flex>
       </Box>
     </Box>
   );
diff --git a/airflow/www/static/js/tree/renderTaskRows.jsx b/airflow/www/static/js/tree/renderTaskRows.jsx
index 224885b..23e52dc 100644
--- a/airflow/www/static/js/tree/renderTaskRows.jsx
+++ b/airflow/www/static/js/tree/renderTaskRows.jsx
@@ -40,7 +40,7 @@ import getMetaValue from '../meta_value';
 const dagId = getMetaValue('dag_id');
 
 const renderTaskRows = ({
-  task, containerRef, level = 0, isParentOpen,
+  task, containerRef, level = 0, isParentOpen, onSelectInstance,
 }) => task.children.map((t) => (
   <Row
     key={t.id}
@@ -49,6 +49,7 @@ const renderTaskRows = ({
     containerRef={containerRef}
     prevTaskId={task.id}
     isParentOpen={isParentOpen}
+    onSelectInstance={onSelectInstance}
   />
 ));
 
@@ -85,7 +86,9 @@ const TaskName = ({
   </Box>
 );
 
-const TaskInstances = ({ task, containerRef, dagRuns }) => (
+const TaskInstances = ({
+  task, containerRef, dagRuns, onSelectInstance,
+}) => (
   <Flex justifyContent="flex-end">
     {dagRuns.map((run) => {
       // Check if an instance exists for the run, or return an empty box
@@ -98,6 +101,7 @@ const TaskInstances = ({ task, containerRef, dagRuns }) => (
             containerRef={containerRef}
             extraLinks={task.extraLinks}
             group={task}
+            onSelectInstance={onSelectInstance}
           />
         )
         : <Box key={`${run.runId}-${task.id}`} width="18px" data-testid="blank-task" />;
@@ -106,7 +110,7 @@ const TaskInstances = ({ task, containerRef, dagRuns }) => (
 );
 
 const Row = ({
-  task, containerRef, level, prevTaskId, isParentOpen = true,
+  task, containerRef, level, prevTaskId, isParentOpen = true, onSelectInstance,
 }) => {
   const { data: { dagRuns = [] } } = useTreeData();
   const isGroup = !!task.children;
@@ -162,13 +166,18 @@ const Row = ({
         <Td width={0} p={0} borderBottom={0} />
         <Td p={0} align="right" _groupHover={{ backgroundColor: 'rgba(113, 128, 150, 0.1)' }} transition="background-color 0.2s" borderBottom={0}>
           <Collapse in={isFullyOpen}>
-            <TaskInstances dagRuns={dagRuns} task={task} containerRef={containerRef} />
+            <TaskInstances
+              dagRuns={dagRuns}
+              task={task}
+              containerRef={containerRef}
+              onSelectInstance={onSelectInstance}
+            />
           </Collapse>
         </Td>
       </Tr>
       {isGroup && (
         renderTaskRows({
-          task, containerRef, level: level + 1, isParentOpen: isOpen,
+          task, containerRef, level: level + 1, isParentOpen: isOpen, onSelectInstance,
         })
       )}
     </>