Skip to content

Commit 440f1c8

Browse files
tirkarthibbovenzi
andauthored
Implement task duration page in react. (#35863)
* Implement task duration page in react. Update icon to match existing task duration page. Update with queued duration. PR comments about variables and using moment to convert duration. Add task row selection support. Implement task duration page in react. Update icon to match existing task duration page. Add task row selection support. Sort as per DagRun order in grid and add logical date as x-axis label. Rearrange task selection tabs fix group/mapped details and select task node if no run Fix tests and remove unneeded rebase changes Fix TI ordering and add dbl click to expand/collapse groups Fix header nav and no TIs Refactor TaskName, hide null tooltips, and add fake tab link * Fix task name selection --------- Co-authored-by: Brent Bovenzi <brent.bovenzi@gmail.com>
1 parent d944eb0 commit 440f1c8

11 files changed

Lines changed: 388 additions & 60 deletions

File tree

airflow/www/static/js/components/ReactECharts.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface ReactEChartsProps {
2828
settings?: SetOptionOpts;
2929
style?: CSSProperties;
3030
theme?: "light" | "dark";
31+
events?: { [key: string]: (params: any) => void };
3132
}
3233

3334
const ReactECharts = ({
@@ -36,6 +37,7 @@ const ReactECharts = ({
3637
settings,
3738
style,
3839
theme,
40+
events,
3941
}: ReactEChartsProps) => {
4042
const ref = useRef<HTMLDivElement>(null);
4143

@@ -78,9 +80,15 @@ const ReactECharts = ({
7880
const chartInstance = getInstanceByDom(ref.current);
7981
if (chartInstance) {
8082
chartInstance.setOption(option, settings);
83+
84+
if (events) {
85+
Object.keys(events).forEach((key) => {
86+
chartInstance.on(key, events[key]);
87+
});
88+
}
8189
}
8290
}
83-
}, [option, settings, theme]);
91+
}, [option, settings, theme, events]);
8492

8593
return (
8694
<div

airflow/www/static/js/dag/TaskName.test.tsx

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,28 +28,25 @@ import TaskName from "./TaskName";
2828

2929
describe("Test TaskName", () => {
3030
test("Displays a normal task name", () => {
31-
const { getByText } = render(
32-
<TaskName label="test" id="test" onToggle={() => {}} />,
33-
{ wrapper: ChakraWrapper }
34-
);
31+
const { getByText } = render(<TaskName label="test" id="test" />, {
32+
wrapper: ChakraWrapper,
33+
});
3534

3635
expect(getByText("test")).toBeDefined();
3736
});
3837

3938
test("Displays a mapped task name", () => {
40-
const { getByText } = render(
41-
<TaskName label="test" id="test" isMapped onToggle={() => {}} />,
42-
{ wrapper: ChakraWrapper }
43-
);
39+
const { getByText } = render(<TaskName label="test" id="test" isMapped />, {
40+
wrapper: ChakraWrapper,
41+
});
4442

4543
expect(getByText("test [ ]")).toBeDefined();
4644
});
4745

4846
test("Displays a group task name", () => {
49-
const { getByText } = render(
50-
<TaskName label="test" id="test" isGroup onToggle={() => {}} />,
51-
{ wrapper: ChakraWrapper }
52-
);
47+
const { getByText } = render(<TaskName label="test" id="test" isGroup />, {
48+
wrapper: ChakraWrapper,
49+
});
5350

5451
expect(getByText("test")).toBeDefined();
5552
});

airflow/www/static/js/dag/TaskName.tsx

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,13 @@
1717
* under the License.
1818
*/
1919

20-
import React, { MouseEventHandler, CSSProperties } from "react";
21-
import { Text, TextProps, useTheme } from "@chakra-ui/react";
20+
import React, { CSSProperties } from "react";
21+
import { Text, TextProps, useTheme, chakra } from "@chakra-ui/react";
2222
import { FiChevronUp, FiArrowUpRight, FiArrowDownRight } from "react-icons/fi";
2323

2424
interface Props extends TextProps {
2525
isGroup?: boolean;
2626
isMapped?: boolean;
27-
onToggle: MouseEventHandler<HTMLDivElement>;
2827
isOpen?: boolean;
2928
label: string;
3029
id?: string;
@@ -35,12 +34,12 @@ interface Props extends TextProps {
3534
const TaskName = ({
3635
isGroup = false,
3736
isMapped = false,
38-
onToggle,
3937
isOpen = false,
4038
label,
4139
id,
4240
setupTeardownType,
4341
isZoomedOut,
42+
onClick,
4443
...rest
4544
}: Props) => {
4645
const { colors } = useTheme();
@@ -51,34 +50,34 @@ const TaskName = ({
5150
};
5251
return (
5352
<Text
54-
cursor={isGroup ? "pointer" : undefined}
55-
onClick={onToggle}
53+
cursor="pointer"
5654
data-testid={id}
57-
width="100%"
5855
color={colors.gray[800]}
5956
fontSize={isZoomedOut ? 24 : undefined}
6057
textAlign="justify"
6158
{...rest}
6259
>
63-
{label}
64-
{isMapped && " [ ]"}
65-
{isGroup && (
66-
<FiChevronUp
67-
size={isZoomedOut ? 24 : 15}
68-
strokeWidth={3}
69-
style={{
70-
transition: "transform 0.5s",
71-
transform: `rotate(${isOpen ? 0 : 180}deg)`,
72-
...iconStyle,
73-
}}
74-
/>
75-
)}
76-
{setupTeardownType === "setup" && (
77-
<FiArrowUpRight size={isZoomedOut ? 24 : 15} style={iconStyle} />
78-
)}
79-
{setupTeardownType === "teardown" && (
80-
<FiArrowDownRight size={isZoomedOut ? 24 : 15} style={iconStyle} />
81-
)}
60+
<chakra.span onClick={onClick}>
61+
{label}
62+
{isMapped && " [ ]"}
63+
{isGroup && (
64+
<FiChevronUp
65+
size={isZoomedOut ? 24 : 15}
66+
strokeWidth={3}
67+
style={{
68+
transition: "transform 0.5s",
69+
transform: `rotate(${isOpen ? 0 : 180}deg)`,
70+
...iconStyle,
71+
}}
72+
/>
73+
)}
74+
{setupTeardownType === "setup" && (
75+
<FiArrowUpRight size={isZoomedOut ? 24 : 15} style={iconStyle} />
76+
)}
77+
{setupTeardownType === "teardown" && (
78+
<FiArrowDownRight size={isZoomedOut ? 24 : 15} style={iconStyle} />
79+
)}
80+
</chakra.span>
8281
</Text>
8382
);
8483
};

airflow/www/static/js/dag/details/Header.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,18 @@ const Header = () => {
4747
} = useSelection();
4848
const dagRun = dagRuns.find((r) => r.runId === runId);
4949

50-
// clearSelection if the current selected dagRun is
51-
// filtered out.
50+
const group = getTask({ taskId, task: groups });
51+
52+
// If runId and/or taskId can't be found remove the selection
5253
useEffect(() => {
53-
if (runId && !dagRun) {
54+
if (runId && !dagRun && taskId && !group) {
5455
clearSelection();
56+
} else if (runId && !dagRun) {
57+
onSelect({ taskId });
58+
} else if (taskId && !group) {
59+
onSelect({ runId });
5560
}
56-
}, [clearSelection, dagRun, runId]);
61+
}, [dagRun, taskId, group, runId, onSelect, clearSelection]);
5762

5863
let runLabel;
5964
if (dagRun && runId) {
@@ -75,15 +80,13 @@ const Header = () => {
7580
);
7681
}
7782

78-
const group = getTask({ taskId, task: groups });
79-
8083
const lastIndex = taskId ? taskId.lastIndexOf(".") : null;
8184
const taskName =
8285
taskId && lastIndex ? taskId.substring(lastIndex + 1) : taskId;
8386

8487
const isDagDetails = !runId && !taskId;
8588
const isRunDetails = !!(runId && !taskId);
86-
const isTaskDetails = runId && taskId && mapIndex === undefined;
89+
const isTaskDetails = !runId && taskId;
8790
const isMappedTaskDetails = runId && taskId && mapIndex !== undefined;
8891

8992
return (
@@ -109,7 +112,11 @@ const Header = () => {
109112
{taskId && (
110113
<BreadcrumbItem isCurrentPage mt={4}>
111114
<BreadcrumbLink
112-
onClick={() => onSelect({ runId, taskId })}
115+
onClick={() =>
116+
mapIndex !== undefined
117+
? onSelect({ runId, taskId })
118+
: onSelect({ taskId })
119+
}
113120
_hover={isTaskDetails ? { cursor: "default" } : undefined}
114121
>
115122
<BreadcrumbText

airflow/www/static/js/dag/details/graph/Node.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export const BaseNode = ({
144144
label={taskName}
145145
isOpen={isOpen}
146146
isGroup={!!childCount}
147-
onToggle={(e) => {
147+
onClick={(e) => {
148148
e.stopPropagation();
149149
onToggleCollapse();
150150
}}

airflow/www/static/js/dag/details/graph/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const flattenNodes = ({
6262
if (!node.id.endsWith("join_id") && selected.runId) {
6363
instance = group?.instances.find((ti) => ti.runId === selected.runId);
6464
}
65-
const isSelected = node.id === selected.taskId && !!instance;
65+
const isSelected = node.id === selected.taskId;
6666
const isActive =
6767
instance && hoveredTaskState !== undefined
6868
? hoveredTaskState === instance.state

airflow/www/static/js/dag/details/index.tsx

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
TabPanels,
2828
Tab,
2929
Text,
30+
Button,
3031
} from "@chakra-ui/react";
3132
import { useSearchParams } from "react-router-dom";
3233

@@ -40,6 +41,7 @@ import {
4041
MdCode,
4142
MdOutlineViewTimeline,
4243
MdSyncAlt,
44+
MdHourglassBottom,
4345
} from "react-icons/md";
4446
import { BiBracket } from "react-icons/bi";
4547
import URLSearchParamsWrapper from "src/utils/URLSearchParamWrapper";
@@ -60,6 +62,7 @@ import MarkRunAs from "./dagRun/MarkRunAs";
6062
import ClearInstance from "./taskInstance/taskActions/ClearInstance";
6163
import MarkInstanceAs from "./taskInstance/taskActions/MarkInstanceAs";
6264
import XcomCollection from "./taskInstance/Xcom";
65+
import TaskDetails from "./task";
6366

6467
const dagId = getMetaValue("dag_id")!;
6568

@@ -92,7 +95,6 @@ const tabToIndex = (tab?: string) => {
9295

9396
const indexToTab = (
9497
index: number,
95-
taskId: string | null,
9698
isTaskInstance: boolean,
9799
isMappedTaskSummary: boolean
98100
) => {
@@ -153,6 +155,7 @@ const Details = ({
153155
!isGroup &&
154156
!isMappedTaskSummary
155157
);
158+
const showTaskDetails = !!taskId && !runId;
156159

157160
const [searchParams, setSearchParams] = useSearchParams();
158161
const tab = searchParams.get(TAB_PARAM) || undefined;
@@ -161,24 +164,20 @@ const Details = ({
161164
const onChangeTab = useCallback(
162165
(index: number) => {
163166
const params = new URLSearchParamsWrapper(searchParams);
164-
const newTab = indexToTab(
165-
index,
166-
taskId,
167-
isTaskInstance,
168-
isMappedTaskSummary
169-
);
167+
const newTab = indexToTab(index, isTaskInstance, isMappedTaskSummary);
170168
if (newTab) params.set(TAB_PARAM, newTab);
171169
else params.delete(TAB_PARAM);
172170
setSearchParams(params);
173171
},
174-
[setSearchParams, searchParams, isTaskInstance, isMappedTaskSummary, taskId]
172+
[setSearchParams, searchParams, isTaskInstance, isMappedTaskSummary]
175173
);
176174

177175
useEffect(() => {
178176
// Default to graph tab when navigating from a task instance to a group/dag/dagrun
179177
const tabCount = runId && taskId && !isGroup ? 5 : 4;
180178
if (tabCount === 4 && tabIndex > 3) {
181-
onChangeTab(1);
179+
if (!runId && taskId) onChangeTab(0);
180+
else onChangeTab(1);
182181
}
183182
}, [runId, taskId, tabIndex, isGroup, onChangeTab]);
184183

@@ -300,6 +299,28 @@ const Details = ({
300299
</Text>
301300
</Tab>
302301
)}
302+
{/* Match the styling of a tab but its actually a button */}
303+
{!!taskId && !!runId && (
304+
<Button
305+
variant="unstyled"
306+
display="flex"
307+
alignItems="center"
308+
fontSize="lg"
309+
py={3}
310+
// need to split pl and pr instead of px
311+
pl={4}
312+
pr={4}
313+
mt="4px"
314+
onClick={() => {
315+
onSelect({ taskId });
316+
}}
317+
>
318+
<MdHourglassBottom size={16} />
319+
<Text as="strong" ml={1}>
320+
Task Duration
321+
</Text>
322+
</Button>
323+
)}
303324
</TabList>
304325
<TabPanels height="100%">
305326
<TabPanel height="100%">
@@ -318,6 +339,7 @@ const Details = ({
318339
/>
319340
</>
320341
)}
342+
{showTaskDetails && <TaskDetails />}
321343
</TabPanel>
322344
<TabPanel p={0} height="100%">
323345
<Graph

0 commit comments

Comments
 (0)