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:28 UTC
[airflow] 01/01: Add queue date and duration timeline
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),
}