Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Expand All @@ -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);
}
});
Expand All @@ -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 />
Expand All @@ -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>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,33 +35,44 @@ 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`;

// 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"
Expand Down Expand Up @@ -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" />
Expand All @@ -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
Expand Down
87 changes: 44 additions & 43 deletions airflow/www/static/js/grid/dagRuns/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import {
Td,
Text,
Box,
Flex,
TextProps,
TableCellProps,
Flex,
BoxProps,
} from '@chakra-ui/react';

import { useGridData } from '../api';
Expand All @@ -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 {
Expand All @@ -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>
);
};
Expand Down
1 change: 0 additions & 1 deletion airflow/www/static/js/grid/renderTaskRows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ const Row = (props: RowProps) => {
/>
</Collapse>
</Td>
<Td width={0} p={0} borderBottom={0} />
<Td
p={0}
align="right"
Expand Down