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

bbovenzi 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 1429091d7c Fix Grid vertical scrolling (#24684)
1429091d7c is described below

commit 1429091d7cf8e16e16efc7a0a3cc00f6ae5716a1
Author: Brent Bovenzi <[email protected]>
AuthorDate: Tue Jun 28 11:43:28 2022 -0400

    Fix Grid vertical scrolling (#24684)
    
    * fix vertical scrolling
    
    * fix flex grow for panel open/close
    
    * add type checking
    
    * add duration axis component
    
    * remove details/grid width changes
    
    this should be done in a separate PR
---
 airflow/www/static/js/grid/{Grid.jsx => Grid.tsx}  | 73 +++++++++---------
 .../static/js/grid/dagRuns/{Bar.jsx => Bar.tsx}    | 29 +++++---
 airflow/www/static/js/grid/dagRuns/index.tsx       | 87 +++++++++++-----------
 airflow/www/static/js/grid/renderTaskRows.tsx      |  1 -
 4 files changed, 103 insertions(+), 87 deletions(-)

diff --git a/airflow/www/static/js/grid/Grid.jsx 
b/airflow/www/static/js/grid/Grid.tsx
similarity index 73%
rename from airflow/www/static/js/grid/Grid.jsx
rename to airflow/www/static/js/grid/Grid.tsx
index 17101cef4f..c32d8230ce 100644
--- a/airflow/www/static/js/grid/Grid.jsx
+++ b/airflow/www/static/js/grid/Grid.tsx
@@ -40,18 +40,24 @@ import AutoRefresh from './AutoRefresh';
 
 const dagId = getMetaValue('dag_id');
 
-const Grid = ({ isPanelOpen = false, onPanelToggle, hoveredTaskState }) => {
-  const scrollRef = useRef();
-  const tableRef = useRef();
+interface Props {
+  isPanelOpen?: boolean;
+  onPanelToggle: () => void;
+  hoveredTaskState?: string;
+}
+
+const Grid = ({ isPanelOpen = false, onPanelToggle, hoveredTaskState }: Props) 
=> {
+  const scrollRef = useRef<HTMLDivElement>(null);
+  const tableRef = useRef<HTMLTableSectionElement>(null);
 
   const { data: { groups, dagRuns } } = useGridData();
   const dagRunIds = dagRuns.map((dr) => dr.runId);
 
   const openGroupsKey = `${dagId}/open-groups`;
-  const storedGroups = JSON.parse(localStorage.getItem(openGroupsKey)) || [];
+  const storedGroups = JSON.parse(localStorage.getItem(openGroupsKey) || '[]');
   const [openGroupIds, setOpenGroupIds] = useState(storedGroups);
 
-  const onToggleGroups = (groupIds) => {
+  const onToggleGroups = (groupIds: string[]) => {
     localStorage.setItem(openGroupsKey, JSON.stringify(groupIds));
     setOpenGroupIds(groupIds);
   };
@@ -60,7 +66,11 @@ const Grid = ({ isPanelOpen = false, onPanelToggle, 
hoveredTaskState }) => {
     const scrollOnResize = new ResizeObserver(() => {
       const runsContainer = scrollRef.current;
       // Set scroll to top right if it is scrollable
-      if (runsContainer && runsContainer.scrollWidth > 
runsContainer.clientWidth) {
+      if (
+        tableRef?.current
+        && runsContainer
+        && runsContainer.scrollWidth > runsContainer.clientWidth
+      ) {
         runsContainer.scrollBy(tableRef.current.offsetWidth, 0);
       }
     });
@@ -74,26 +84,21 @@ const Grid = ({ isPanelOpen = false, onPanelToggle, 
hoveredTaskState }) => {
       };
     }
     return () => {};
-  }, [tableRef]);
+  }, [tableRef, isPanelOpen]);
 
   return (
     <Box
-      position="relative"
+      minWidth={isPanelOpen ? '350px' : undefined}
+      flexGrow={1}
       m={3}
       mt={0}
-      overflow="auto"
-      ref={scrollRef}
-      flexGrow={1}
-      minWidth={isPanelOpen && '350px'}
     >
       <Flex
         alignItems="center"
         justifyContent="space-between"
-        position="sticky"
-        top={0}
-        left={0}
         mb={2}
         p={1}
+        backgroundColor="white"
       >
         <Flex alignItems="center">
           <AutoRefresh />
@@ -110,28 +115,28 @@ const Grid = ({ isPanelOpen = false, onPanelToggle, 
hoveredTaskState }) => {
           title={`${isPanelOpen ? 'Hide ' : 'Show '} Details Panel`}
           aria-label={isPanelOpen ? 'Show Details' : 'Hide Details'}
           icon={<MdReadMore />}
-          transform={!isPanelOpen && 'rotateZ(180deg)'}
+          transform={!isPanelOpen ? 'rotateZ(180deg)' : undefined}
           transitionProperty="none"
         />
       </Flex>
-      <Table>
-        <Thead display="block" pr="10px" position="sticky" top={0} zIndex={2} 
bg="white">
-          <DagRuns />
-        </Thead>
-        {/* TODO: remove hardcoded values. 665px is roughly the total 
heade+footer height */}
-        <Tbody
-          display="block"
-          width="100%"
-          maxHeight="calc(100vh - 665px)"
-          minHeight="500px"
-          ref={tableRef}
-          pr="10px"
-        >
-          {renderTaskRows({
-            task: groups, dagRunIds, openGroupIds, onToggleGroups, 
hoveredTaskState,
-          })}
-        </Tbody>
-      </Table>
+      <Box
+        overflow="auto"
+        ref={scrollRef}
+        maxHeight="900px"
+        position="relative"
+      >
+        <Table pr="10px">
+          <Thead>
+            <DagRuns />
+          </Thead>
+          {/* TODO: remove hardcoded values. 665px is roughly the total 
heade+footer height */}
+          <Tbody ref={tableRef}>
+            {renderTaskRows({
+              task: groups, dagRunIds, openGroupIds, onToggleGroups, 
hoveredTaskState,
+            })}
+          </Tbody>
+        </Table>
+      </Box>
     </Box>
   );
 };
diff --git a/airflow/www/static/js/grid/dagRuns/Bar.jsx 
b/airflow/www/static/js/grid/dagRuns/Bar.tsx
similarity index 82%
rename from airflow/www/static/js/grid/dagRuns/Bar.jsx
rename to airflow/www/static/js/grid/dagRuns/Bar.tsx
index 22163a44da..18399ab885 100644
--- a/airflow/www/static/js/grid/dagRuns/Bar.jsx
+++ b/airflow/www/static/js/grid/dagRuns/Bar.tsx
@@ -35,12 +35,23 @@ import { RiArrowGoBackFill } from 'react-icons/ri';
 import DagRunTooltip from './Tooltip';
 import { useContainerRef } from '../context/containerRef';
 import Time from '../components/Time';
+import type { SelectionProps } from '../utils/useSelection';
+import type { RunWithDuration } from '.';
 
 const BAR_HEIGHT = 100;
 
+interface Props {
+  run: RunWithDuration
+  max: number;
+  index: number;
+  totalRuns: number;
+  isSelected: boolean;
+  onSelect: (props: SelectionProps) => void;
+}
+
 const DagRunBar = ({
   run, max, index, totalRuns, isSelected, onSelect,
-}) => {
+}: Props) => {
   const containerRef = useContainerRef();
   const { colors } = useTheme();
   const hoverBlue = `${colors.blue[100]}50`;
@@ -48,20 +59,20 @@ const DagRunBar = ({
   // Fetch the corresponding column element and set its background color when 
hovering
   const onMouseEnter = () => {
     if (!isSelected) {
-      [...containerRef.current.getElementsByClassName(`js-${run.runId}`)]
-        .forEach((e) => { e.style.backgroundColor = hoverBlue; });
+      const els = 
Array.from(containerRef?.current?.getElementsByClassName(`js-${run.runId}`) as 
HTMLCollectionOf<HTMLElement>);
+      els.forEach((e) => { e.style.backgroundColor = hoverBlue; });
     }
   };
   const onMouseLeave = () => {
-    [...containerRef.current.getElementsByClassName(`js-${run.runId}`)]
-      .forEach((e) => { e.style.backgroundColor = null; });
+    const els = 
Array.from(containerRef?.current?.getElementsByClassName(`js-${run.runId}`) as 
HTMLCollectionOf<HTMLElement>);
+    els.forEach((e) => { e.style.backgroundColor = ''; });
   };
 
   return (
     <Box
       className={`js-${run.runId}`}
       data-selected={isSelected}
-      bg={isSelected && 'blue.100'}
+      bg={isSelected ? 'blue.100' : undefined}
       transition="background-color 0.2s"
       px="1px"
       pb="2px"
@@ -106,7 +117,7 @@ const DagRunBar = ({
           </Flex>
         </Tooltip>
       </Flex>
-      {index < totalRuns - 3 && index % 10 === 0 && (
+      {(index === totalRuns - 4 || (index + 4) % 10 === 0) && (
       <VStack position="absolute" top="0" left="8px" spacing={0} zIndex={0} 
width={0}>
         <Text fontSize="sm" color="gray.400" whiteSpace="nowrap" 
transform="rotate(-30deg) translateX(28px)" mt="-23px !important">
           <Time dateTime={run.executionDate} format="MMM DD, HH:mm" />
@@ -121,8 +132,8 @@ const DagRunBar = ({
 // The default equality function is a shallow comparison and json objects will 
return false
 // This custom compare function allows us to do a deeper comparison
 const compareProps = (
-  prevProps,
-  nextProps,
+  prevProps: Props,
+  nextProps: Props,
 ) => (
   isEqual(prevProps.run, nextProps.run)
   && prevProps.max === nextProps.max
diff --git a/airflow/www/static/js/grid/dagRuns/index.tsx 
b/airflow/www/static/js/grid/dagRuns/index.tsx
index 679b7db3a5..6b779175f8 100644
--- a/airflow/www/static/js/grid/dagRuns/index.tsx
+++ b/airflow/www/static/js/grid/dagRuns/index.tsx
@@ -23,8 +23,10 @@ import {
   Td,
   Text,
   Box,
-  Flex,
   TextProps,
+  TableCellProps,
+  Flex,
+  BoxProps,
 } from '@chakra-ui/react';
 
 import { useGridData } from '../api';
@@ -33,17 +35,29 @@ import { getDuration, formatDuration } from 
'../../datetime_utils';
 import useSelection from '../utils/useSelection';
 import type { DagRun } from '../types';
 
+const DurationAxis = (props: BoxProps) => (
+  <Box position="absolute" borderBottomWidth={1} zIndex={0} opacity={0.7} 
width="100%" {...props} />
+);
+
 const DurationTick = ({ children, ...rest }: TextProps) => (
   <Text fontSize="sm" color="gray.400" right={1} position="absolute" 
whiteSpace="nowrap" {...rest}>
     {children}
   </Text>
 );
 
+const Th = (props: TableCellProps) => (
+  <Td position="sticky" top={0} zIndex={1} p={0} height="155px" bg="white" 
{...props} />
+);
+
+export interface RunWithDuration extends DagRun {
+  duration: number;
+}
+
 const DagRuns = () => {
   const { data: { dagRuns } } = useGridData();
   const { selected, onSelect } = useSelection();
   const durations: number[] = [];
-  const runs = dagRuns.map((dagRun) => {
+  const runs: RunWithDuration[] = dagRuns.map((dagRun) => {
     const duration = getDuration(dagRun.startDate, dagRun.endDate);
     durations.push(duration);
     return {
@@ -54,58 +68,45 @@ const DagRuns = () => {
 
   // calculate dag run bar heights relative to max
   const max = Math.max.apply(null, durations);
-  const tickWidth = `${runs.length * 16}px`;
 
   return (
-    <Tr
-      borderBottomWidth={3}
-      position="relative"
-    >
-      <Td
-        height="155px"
-        p={0}
-        position="sticky"
-        left={0}
-        backgroundColor="white"
-        zIndex={2}
-        borderBottom={0}
-        width="100%"
-      >
-        {!!runs.length && (
-        <>
-          <DurationTick bottom="120px">Duration</DurationTick>
-          <DurationTick bottom="96px">
-            {formatDuration(max)}
-          </DurationTick>
-          <DurationTick bottom="46px">
-            {formatDuration(max / 2)}
-          </DurationTick>
-          <DurationTick bottom={0}>
-            00:00:00
-          </DurationTick>
-        </>
-        )}
-      </Td>
-      <Td p={0} borderBottom={0}>
-        <Box position="absolute" bottom="100px" borderBottomWidth={1} 
zIndex={0} opacity={0.7} width={tickWidth} />
-        <Box position="absolute" bottom="50px" borderBottomWidth={1} 
zIndex={0} opacity={0.7} width={tickWidth} />
-        <Box position="absolute" bottom="4px" borderBottomWidth={1} zIndex={0} 
opacity={0.7} width={tickWidth} />
-      </Td>
-      <Td p={0} align="right" verticalAlign="bottom" borderBottom={0} 
width={`${runs.length * 16}px`}>
-        <Flex justifyContent="flex-end">
-          {runs.map((run: DagRun, i: number) => (
+    <Tr>
+      <Th left={0} zIndex={2}>
+        <Box borderBottomWidth={3} position="relative" height="100%" 
width="100%">
+          {!!runs.length && (
+          <>
+            <DurationTick bottom="120px">Duration</DurationTick>
+            <DurationTick bottom="96px">
+              {formatDuration(max)}
+            </DurationTick>
+            <DurationTick bottom="46px">
+              {formatDuration(max / 2)}
+            </DurationTick>
+            <DurationTick bottom={0}>
+              00:00:00
+            </DurationTick>
+          </>
+          )}
+        </Box>
+      </Th>
+      <Th align="right" verticalAlign="bottom">
+        <Flex justifyContent="flex-end" borderBottomWidth={3} 
position="relative">
+          {runs.map((run: RunWithDuration, index) => (
             <DagRunBar
               key={run.runId}
               run={run}
-              max={max}
-              index={i}
+              index={index}
               totalRuns={runs.length}
+              max={max}
               isSelected={run.runId === selected.runId}
               onSelect={onSelect}
             />
           ))}
+          <DurationAxis bottom="100px" />
+          <DurationAxis bottom="50px" />
+          <DurationAxis bottom="4px" />
         </Flex>
-      </Td>
+      </Th>
     </Tr>
   );
 };
diff --git a/airflow/www/static/js/grid/renderTaskRows.tsx 
b/airflow/www/static/js/grid/renderTaskRows.tsx
index 11658e873f..cfbae119da 100644
--- a/airflow/www/static/js/grid/renderTaskRows.tsx
+++ b/airflow/www/static/js/grid/renderTaskRows.tsx
@@ -173,7 +173,6 @@ const Row = (props: RowProps) => {
             level={level}
           />
         </Td>
-        <Td width={0} p={0} borderBottom={0} />
         <Td
           p={0}
           align="right"

Reply via email to