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

ephraimanierobi pushed a commit to branch v2-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit b73c38cc358b36e797eca4dc2a9847740244c913
Author: Brent Bovenzi <brent.bove...@gmail.com>
AuthorDate: Tue Jun 28 14:50:02 2022 -0400

    Debounce status highlighting in Grid view (#24710)
    
    * Add delay / debounce to not always highlight tasks
    
    * fix linting
    
    * single delay variable at 200ms
    
    (cherry picked from commit c7feb31786c7744da91d319f499d9f6015d82454)
---
 airflow/www/static/js/grid/Grid.tsx                |  3 +--
 .../{LegendRow.test.jsx => LegendRow.test.tsx}     | 15 +++++++-----
 .../js/grid/{LegendRow.jsx => LegendRow.tsx}       | 27 ++++++++++++++++------
 airflow/www/static/js/grid/Main.tsx                | 15 +++++++++---
 .../www/static/js/grid/components/Clipboard.jsx    |  1 -
 .../www/static/js/grid/components/StatusBox.tsx    |  3 ++-
 airflow/www/static/js/grid/dagRuns/Bar.tsx         |  3 ++-
 airflow/www/static/js/grid/renderTaskRows.tsx      |  4 ++--
 airflow/www/static/js/grid/utils/index.ts          | 26 +++++++++++++++++++++
 9 files changed, 74 insertions(+), 23 deletions(-)

diff --git a/airflow/www/static/js/grid/Grid.tsx 
b/airflow/www/static/js/grid/Grid.tsx
index c32d8230ce..6de3d0ecc2 100644
--- a/airflow/www/static/js/grid/Grid.tsx
+++ b/airflow/www/static/js/grid/Grid.tsx
@@ -43,7 +43,7 @@ const dagId = getMetaValue('dag_id');
 interface Props {
   isPanelOpen?: boolean;
   onPanelToggle: () => void;
-  hoveredTaskState?: string;
+  hoveredTaskState?: string | null;
 }
 
 const Grid = ({ isPanelOpen = false, onPanelToggle, hoveredTaskState }: Props) 
=> {
@@ -129,7 +129,6 @@ const Grid = ({ isPanelOpen = false, onPanelToggle, 
hoveredTaskState }: Props) =
           <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,
diff --git a/airflow/www/static/js/grid/LegendRow.test.jsx 
b/airflow/www/static/js/grid/LegendRow.test.tsx
similarity index 76%
rename from airflow/www/static/js/grid/LegendRow.test.jsx
rename to airflow/www/static/js/grid/LegendRow.test.tsx
index bd1c64d0c3..7a3ba18593 100644
--- a/airflow/www/static/js/grid/LegendRow.test.jsx
+++ b/airflow/www/static/js/grid/LegendRow.test.tsx
@@ -20,14 +20,16 @@
 /* global describe, test, expect, stateColors, jest */
 
 import React from 'react';
-import { render, fireEvent } from '@testing-library/react';
+import { render, fireEvent, waitFor } from '@testing-library/react';
 
 import LegendRow from './LegendRow';
 
 describe('Test LegendRow', () => {
   test('Render displays correctly the different task states', () => {
+    const onStatusHover = jest.fn();
+    const onStatusLeave = jest.fn();
     const { getByText } = render(
-      <LegendRow />,
+      <LegendRow onStatusHover={onStatusHover} onStatusLeave={onStatusLeave} 
/>,
     );
 
     Object.keys(stateColors).forEach((taskState) => {
@@ -44,15 +46,16 @@ describe('Test LegendRow', () => {
   ])(
     'Hovering $state badge should trigger setHoverdTaskState function with 
$expectedSetValue',
     async ({ state, expectedSetValue }) => {
-      const setHoveredTaskState = jest.fn();
+      const onStatusHover = jest.fn();
+      const onStatusLeave = jest.fn();
       const { getByText } = render(
-        <LegendRow setHoveredTaskState={setHoveredTaskState} />,
+        <LegendRow onStatusHover={onStatusHover} onStatusLeave={onStatusLeave} 
/>,
       );
       const successElement = getByText(state);
       fireEvent.mouseEnter(successElement);
-      expect(setHoveredTaskState).toHaveBeenCalledWith(expectedSetValue);
+      await waitFor(() => 
expect(onStatusHover).toHaveBeenCalledWith(expectedSetValue));
       fireEvent.mouseLeave(successElement);
-      expect(setHoveredTaskState).toHaveBeenLastCalledWith();
+      await waitFor(() => expect(onStatusLeave).toHaveBeenLastCalledWith());
     },
   );
 });
diff --git a/airflow/www/static/js/grid/LegendRow.jsx 
b/airflow/www/static/js/grid/LegendRow.tsx
similarity index 71%
rename from airflow/www/static/js/grid/LegendRow.jsx
rename to airflow/www/static/js/grid/LegendRow.tsx
index eff503403c..ade94b0592 100644
--- a/airflow/www/static/js/grid/LegendRow.jsx
+++ b/airflow/www/static/js/grid/LegendRow.tsx
@@ -26,23 +26,34 @@ import {
 } from '@chakra-ui/react';
 import React from 'react';
 
+interface LegendProps {
+  onStatusHover: (status: string | null) => void;
+  onStatusLeave: () => void;
+}
+
+interface BadgeProps extends LegendProps {
+  state: string | null;
+  stateColor: string;
+  displayValue?: string;
+}
+
 const StatusBadge = ({
-  state, stateColor, setHoveredTaskState, displayValue,
-}) => (
+  state, stateColor, onStatusHover, onStatusLeave, displayValue,
+}: BadgeProps) => (
   <Text
     borderRadius={4}
     border={`solid 2px ${stateColor}`}
     px={1}
     cursor="pointer"
     fontSize="11px"
-    onMouseEnter={() => setHoveredTaskState(state)}
-    onMouseLeave={() => setHoveredTaskState()}
+    onMouseEnter={() => onStatusHover(state)}
+    onMouseLeave={() => onStatusLeave()}
   >
     {displayValue || state }
   </Text>
 );
 
-const LegendRow = ({ setHoveredTaskState }) => (
+const LegendRow = ({ onStatusHover, onStatusLeave }: LegendProps) => (
   <Flex p={4} flexWrap="wrap" justifyContent="end">
     <HStack spacing={2} wrap="wrap">
       {
@@ -51,7 +62,8 @@ const LegendRow = ({ setHoveredTaskState }) => (
           key={state}
           state={state}
           stateColor={stateColor}
-          setHoveredTaskState={setHoveredTaskState}
+          onStatusHover={onStatusHover}
+          onStatusLeave={onStatusLeave}
         />
       ))
       }
@@ -60,7 +72,8 @@ const LegendRow = ({ setHoveredTaskState }) => (
         displayValue="no_status"
         state={null}
         stateColor="white"
-        setHoveredTaskState={setHoveredTaskState}
+        onStatusHover={onStatusHover}
+        onStatusLeave={onStatusLeave}
       />
     </HStack>
   </Flex>
diff --git a/airflow/www/static/js/grid/Main.tsx 
b/airflow/www/static/js/grid/Main.tsx
index 8779b547ec..bde36c2984 100644
--- a/airflow/www/static/js/grid/Main.tsx
+++ b/airflow/www/static/js/grid/Main.tsx
@@ -27,7 +27,7 @@ import {
   Divider,
   Spinner,
 } from '@chakra-ui/react';
-import { isEmpty } from 'lodash';
+import { isEmpty, debounce } from 'lodash';
 
 import Details from './details';
 import useSelection from './utils/useSelection';
@@ -35,6 +35,7 @@ import Grid from './Grid';
 import FilterBar from './FilterBar';
 import LegendRow from './LegendRow';
 import { useGridData } from './api';
+import { hoverDelay } from './utils';
 
 const detailsPanelKey = 'hideDetailsPanel';
 
@@ -43,7 +44,15 @@ const Main = () => {
   const isPanelOpen = localStorage.getItem(detailsPanelKey) !== 'true';
   const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: isPanelOpen });
   const { clearSelection } = useSelection();
-  const [hoveredTaskState, setHoveredTaskState] = useState();
+  const [hoveredTaskState, setHoveredTaskState] = useState<string | null | 
undefined>();
+
+  // Add a debounced delay to not constantly trigger highlighting certain task 
states
+  const onStatusHover = debounce((state) => setHoveredTaskState(state), 
hoverDelay);
+
+  const onStatusLeave = () => {
+    setHoveredTaskState(undefined);
+    onStatusHover.cancel();
+  };
 
   const onPanelToggle = () => {
     if (!isOpen) {
@@ -58,7 +67,7 @@ const Main = () => {
   return (
     <Box>
       <FilterBar />
-      <LegendRow setHoveredTaskState={setHoveredTaskState} />
+      <LegendRow onStatusHover={onStatusHover} onStatusLeave={onStatusLeave} />
       <Divider mb={5} borderBottomWidth={2} />
       <Flex justifyContent="space-between">
         {isLoading || isEmpty(groups)
diff --git a/airflow/www/static/js/grid/components/Clipboard.jsx 
b/airflow/www/static/js/grid/components/Clipboard.jsx
index 794e363fa0..a4e0acca28 100644
--- a/airflow/www/static/js/grid/components/Clipboard.jsx
+++ b/airflow/www/static/js/grid/components/Clipboard.jsx
@@ -60,7 +60,6 @@ export const ClipboardButton = forwardRef(
         label="Copied"
         isOpen={hasCopied}
         isDisabled={!hasCopied}
-        closeDelay={500}
         placement="top"
         portalProps={{ containerRef }}
       >
diff --git a/airflow/www/static/js/grid/components/StatusBox.tsx 
b/airflow/www/static/js/grid/components/StatusBox.tsx
index f9acf57402..dd8dfabfb1 100644
--- a/airflow/www/static/js/grid/components/StatusBox.tsx
+++ b/airflow/www/static/js/grid/components/StatusBox.tsx
@@ -30,6 +30,7 @@ import InstanceTooltip from './InstanceTooltip';
 import { useContainerRef } from '../context/containerRef';
 import type { Task, TaskInstance, TaskState } from '../types';
 import type { SelectionProps } from '../utils/useSelection';
+import { hoverDelay } from '../utils';
 
 export const boxSize = 10;
 export const boxSizePx = `${boxSize}px`;
@@ -92,7 +93,7 @@ const StatusBox = ({
       portalProps={{ containerRef }}
       hasArrow
       placement="top"
-      openDelay={400}
+      openDelay={hoverDelay}
     >
       <Box>
         <SimpleStatus
diff --git a/airflow/www/static/js/grid/dagRuns/Bar.tsx 
b/airflow/www/static/js/grid/dagRuns/Bar.tsx
index 18399ab885..00c5214b83 100644
--- a/airflow/www/static/js/grid/dagRuns/Bar.tsx
+++ b/airflow/www/static/js/grid/dagRuns/Bar.tsx
@@ -37,6 +37,7 @@ import { useContainerRef } from '../context/containerRef';
 import Time from '../components/Time';
 import type { SelectionProps } from '../utils/useSelection';
 import type { RunWithDuration } from '.';
+import { hoverDelay } from '../utils';
 
 const BAR_HEIGHT = 100;
 
@@ -95,7 +96,7 @@ const DagRunBar = ({
           hasArrow
           portalProps={{ containerRef }}
           placement="top"
-          openDelay={100}
+          openDelay={hoverDelay}
         >
           <Flex
             width="10px"
diff --git a/airflow/www/static/js/grid/renderTaskRows.tsx 
b/airflow/www/static/js/grid/renderTaskRows.tsx
index cfbae119da..f35d1a2c9e 100644
--- a/airflow/www/static/js/grid/renderTaskRows.tsx
+++ b/airflow/www/static/js/grid/renderTaskRows.tsx
@@ -43,7 +43,7 @@ interface RowProps {
   openParentCount?: number;
   openGroupIds?: string[];
   onToggleGroups?: (groupIds: string[]) => void;
-  hoveredTaskState?: string;
+  hoveredTaskState?: string | null;
 }
 
 const renderTaskRows = ({
@@ -67,7 +67,7 @@ interface TaskInstancesProps {
   dagRunIds: string[];
   selectedRunId?: string | null;
   onSelect: (selection: SelectionProps) => void;
-  hoveredTaskState?: string;
+  hoveredTaskState?: string | null;
 }
 
 const TaskInstances = ({
diff --git a/airflow/www/static/js/grid/utils/index.ts 
b/airflow/www/static/js/grid/utils/index.ts
new file mode 100644
index 0000000000..165de55616
--- /dev/null
+++ b/airflow/www/static/js/grid/utils/index.ts
@@ -0,0 +1,26 @@
+/* eslint-disable import/prefer-default-export */
+/*!
+ * 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.
+ */
+
+// Delay in ms for various hover actions
+const hoverDelay = 200;
+
+export {
+  hoverDelay,
+};

Reply via email to