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/04/19 21:43:27 UTC

[airflow] branch grid-timeline created (now f49baa3939)

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

bbovenzi pushed a change to branch grid-timeline
in repository https://gitbox.apache.org/repos/asf/airflow.git


      at f49baa3939 Add queue date and duration timeline

This branch includes the following new commits:

     new f49baa3939 Add queue date and duration timeline

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[airflow] 01/01: Add queue date and duration timeline

Posted by bb...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit f49baa39392a1e489c1f547730ce3fe9f7a789e2
Author: Brent Bovenzi <br...@gmail.com>
AuthorDate: Tue Apr 19 17:37:48 2022 -0400

    Add queue date and duration timeline
---
 .../tree/details/content/taskInstance/Details.jsx  |  23 ++--
 .../content/taskInstance/MappedInstances.jsx       |  24 ++--
 .../tree/details/content/taskInstance/Timeline.jsx | 122 +++++++++++++++++++++
 airflow/www/static/js/tree/details/index.jsx       |   2 +-
 airflow/www/utils.py                               |   5 +
 5 files changed, 154 insertions(+), 22 deletions(-)

diff --git a/airflow/www/static/js/tree/details/content/taskInstance/Details.jsx b/airflow/www/static/js/tree/details/content/taskInstance/Details.jsx
index 6d341e4c3c..d0686bb779 100644
--- a/airflow/www/static/js/tree/details/content/taskInstance/Details.jsx
+++ b/airflow/www/static/js/tree/details/content/taskInstance/Details.jsx
@@ -25,10 +25,10 @@ import {
 } from '@chakra-ui/react';
 
 import { finalStatesMap } from '../../../../utils';
-import { getDuration, formatDuration } from '../../../../datetime_utils';
 import { SimpleStatus } from '../../../StatusBox';
-import Time from '../../../Time';
 import { ClipboardText } from '../../../Clipboard';
+import Timeline from './Timeline';
+import Time from '../../../Time';
 
 const Details = ({ instance, group, operator }) => {
   const isGroup = !!group.children;
@@ -37,11 +37,11 @@ const Details = ({ instance, group, operator }) => {
   const {
     taskId,
     runId,
-    duration,
     startDate,
     endDate,
     state,
     mappedStates,
+    queueDate,
   } = instance;
 
   const {
@@ -81,8 +81,8 @@ const Details = ({ instance, group, operator }) => {
   });
 
   const taskIdTitle = isGroup ? 'Task Group Id: ' : 'Task Id: ';
-  const isStateFinal = ['success', 'failed', 'upstream_failed', 'skipped'].includes(state);
   const isOverall = (isMapped || isGroup) && 'Overall ';
+  const isStateFinal = ['success', 'failed', 'upstream_failed', 'skipped'].includes(state);
 
   return (
     <Flex flexWrap="wrap" justifyContent="space-between">
@@ -130,12 +130,21 @@ const Details = ({ instance, group, operator }) => {
           </Text>
         )}
         <br />
+        <Timeline
+          startDate={startDate}
+          endDate={endDate}
+          queueDate={queueDate}
+          state={state}
+          isOverall={isOverall}
+        />
+        <br />
+        {queueDate && (
         <Text>
-          {isOverall}
-          Duration:
+          Queued:
           {' '}
-          {formatDuration(duration || getDuration(startDate, endDate))}
+          <Time dateTime={queueDate} />
         </Text>
+        )}
         {startDate && (
         <Text>
           Started:
diff --git a/airflow/www/static/js/tree/details/content/taskInstance/MappedInstances.jsx b/airflow/www/static/js/tree/details/content/taskInstance/MappedInstances.jsx
index 42bbdca66f..74d01a45da 100644
--- a/airflow/www/static/js/tree/details/content/taskInstance/MappedInstances.jsx
+++ b/airflow/www/static/js/tree/details/content/taskInstance/MappedInstances.jsx
@@ -31,10 +31,10 @@ import {
 } from 'react-icons/md';
 
 import { getMetaValue } from '../../../../utils';
-import { formatDateTime, formatDuration } from '../../../../datetime_utils';
 import { useMappedInstances } from '../../../api';
 import { SimpleStatus } from '../../../StatusBox';
 import Table from '../../../Table';
+import Timeline from './Timeline';
 
 const renderedTemplatesUrl = getMetaValue('rendered_templates_url');
 const logUrl = getMetaValue('log_url');
@@ -83,9 +83,15 @@ const MappedInstances = ({
             {mi.state || 'no status'}
           </Flex>
         ),
-        duration: mi.duration && formatDuration(mi.duration),
-        startDate: mi.startDate && formatDateTime(mi.startDate),
-        endDate: mi.endDate && formatDateTime(mi.endDate),
+        duration: (
+          <Timeline
+            queueDate={mi.queuedWhen}
+            startDate={mi.startDate}
+            endDate={mi.endDate}
+            state={mi.state}
+            showDates
+          />
+        ),
         links: (
           <Flex alignItems="center">
             <IconLink mr={1} title="Details" aria-label="Details" icon={<MdDetails />} href={detailsLink} />
@@ -114,16 +120,6 @@ const MappedInstances = ({
         accessor: 'duration',
         disableSortBy: true,
       },
-      {
-        Header: 'Start Date',
-        accessor: 'startDate',
-        disableSortBy: true,
-      },
-      {
-        Header: 'End Date',
-        accessor: 'endDate',
-        disableSortBy: true,
-      },
       {
         disableSortBy: true,
         accessor: 'links',
diff --git a/airflow/www/static/js/tree/details/content/taskInstance/Timeline.jsx b/airflow/www/static/js/tree/details/content/taskInstance/Timeline.jsx
new file mode 100644
index 0000000000..d9287d8940
--- /dev/null
+++ b/airflow/www/static/js/tree/details/content/taskInstance/Timeline.jsx
@@ -0,0 +1,122 @@
+/*!
+ * 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 stateColors */
+
+import React from 'react';
+import {
+  Flex,
+  Text,
+  Box,
+  Tooltip,
+  Center,
+} from '@chakra-ui/react';
+
+import { useContainerRef } from '../../../context/containerRef';
+import { getDuration, formatDuration } from '../../../../datetime_utils';
+import Time from '../../../Time';
+
+const Timeline = ({
+  startDate,
+  endDate,
+  queueDate,
+  state,
+  width = 100,
+  isOverall = false,
+  showDates = false,
+}) => {
+  const containerRef = useContainerRef();
+
+  const isStateFinal = ['success', 'failed', 'upstream_failed', 'skipped'].includes(state);
+  const queuedTime = getDuration(queueDate, startDate);
+  const executionTime = getDuration(startDate, endDate);
+  const elapsedTime = getDuration(queueDate, endDate);
+  return (
+    <Center position="relative" width="150px" mb={6} color="gray.400">
+      <Box position="absolute" left="3px" bottom="-22px" textAlign="center">
+        <Text fontSize="xs">|</Text>
+        <Text fontSize="sm"><Time dateTime={queueDate} format="HH:mm:ss" /></Text>
+      </Box>
+      <Box position="absolute" right="3px" bottom="-22px" textAlign="center">
+        <Text fontSize="xs">|</Text>
+        <Text fontSize="sm"><Time dateTime={endDate} format="HH:mm:ss" /></Text>
+      </Box>
+      <Tooltip
+        label={(
+          <>
+            {!queueDate && !startDate && !endDate && (<Text>Instance has not started yet.</Text>)}
+            {showDates && (
+              <>
+                {queueDate && (
+                <Text>
+                  Queued:
+                  {' '}
+                  <Time dateTime={queueDate} />
+                </Text>
+                )}
+                {startDate && (
+                <Text>
+                  Started:
+                  {' '}
+                  <Time dateTime={startDate} />
+                </Text>
+                )}
+                {endDate && isStateFinal && (
+                <Text>
+                  Ended:
+                  {' '}
+                  <Time dateTime={endDate} />
+                </Text>
+                )}
+                <br />
+              </>
+            )}
+            <Text>
+              Queued Time:
+              {' '}
+              {formatDuration(getDuration(queueDate, startDate))}
+            </Text>
+            <Text>
+              {isOverall}
+              Duration:
+              {' '}
+              {formatDuration(getDuration(startDate, endDate))}
+            </Text>
+            <Text>
+              Total Elapsed Time:
+              {' '}
+              {formatDuration(getDuration(queueDate, endDate))}
+            </Text>
+          </>
+      )}
+        hasArrow
+        portalProps={{ containerRef }}
+        placement="bottom"
+        openDelay={100}
+      >
+        <Flex maxWidth={`${width}px`} height={6} borderRadius={3}>
+          <Box height="100%" bg={stateColors.queued} opacity={0.3} width={`${(queuedTime / elapsedTime) * width}px`} minWidth={2} />
+          <Box height="100%" bg={stateColors[state]} width={`${(executionTime / elapsedTime) * width}px`} minWidth={2} />
+        </Flex>
+      </Tooltip>
+    </Center>
+  );
+};
+
+export default Timeline;
diff --git a/airflow/www/static/js/tree/details/index.jsx b/airflow/www/static/js/tree/details/index.jsx
index ffe8b0ff74..18cd329950 100644
--- a/airflow/www/static/js/tree/details/index.jsx
+++ b/airflow/www/static/js/tree/details/index.jsx
@@ -36,7 +36,7 @@ const Details = () => {
     <Flex borderLeftWidth="1px" flexDirection="column" pl={3} mr={3} flexGrow={1} maxWidth="750px">
       <Header />
       <Divider my={2} />
-      <Box minWidth="750px">
+      <Box minWidth="550px">
         {!selected.runId && !selected.taskId && <DagContent />}
         {selected.runId && !selected.taskId && (
           <DagRunContent runId={selected.runId} />
diff --git a/airflow/www/utils.py b/airflow/www/utils.py
index 8a5cc3c717..73722511c6 100644
--- a/airflow/www/utils.py
+++ b/airflow/www/utils.py
@@ -103,6 +103,9 @@ def get_mapped_summary(parent_instance, task_instances):
             group_state = state
             break
 
+    group_queue_date = datetime_to_string(
+        min((ti.queued_dttm for ti in task_instances if ti.queued_dttm), default=None)
+    )
     group_start_date = datetime_to_string(
         min((ti.start_date for ti in task_instances if ti.start_date), default=None)
     )
@@ -120,6 +123,7 @@ def get_mapped_summary(parent_instance, task_instances):
         'run_id': parent_instance.run_id,
         'state': group_state,
         'start_date': group_start_date,
+        'queue_date': group_queue_date,
         'end_date': group_end_date,
         'mapped_states': mapped_states,
         'try_number': try_count,
@@ -149,6 +153,7 @@ def encode_ti(
         'start_date': datetime_to_string(task_instance.start_date),
         'end_date': datetime_to_string(task_instance.end_date),
         'try_number': try_count,
+        'queue_date': datetime_to_string(task_instance.queued_dttm),
     }