Skip to content

Commit 992aecf

Browse files
bbovenzinailo2c
authored andcommitted
Default to unpause dag on trigger,materialize,backfill (apache#47627)
* Default to unpause dag on trigger,materialize,backfill * Explicitly set isPaused to false on modals
1 parent d6d4e74 commit 992aecf

File tree

7 files changed

+127
-50
lines changed

7 files changed

+127
-50
lines changed

airflow/ui/src/components/Graph/DagNode.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,7 @@ export const DagNode = ({
4747
>
4848
<HStack alignItems="center" justifyContent="space-between">
4949
<DagIcon />
50-
<TogglePause
51-
dagId={dag?.dag_id ?? label}
52-
disabled={!Boolean(dag)}
53-
isPaused={dag?.is_paused ?? false}
54-
/>
50+
<TogglePause dagId={dag?.dag_id ?? label} disabled={!Boolean(dag)} isPaused={dag?.is_paused} />
5551
</HStack>
5652
<Link asChild color="fg.info" mb={2}>
5753
<RouterLink to={`/dags/${dag?.dag_id ?? label}`}>{dag?.dag_display_name ?? label}</RouterLink>

airflow/ui/src/components/Menu/RunBackfillForm.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { Alert, Button } from "src/components/ui";
2525
import { reprocessBehaviors } from "src/constants/reprocessBehaviourParams";
2626
import { useCreateBackfill } from "src/queries/useCreateBackfill";
2727
import { useCreateBackfillDryRun } from "src/queries/useCreateBackfillDryRun";
28+
import { useTogglePause } from "src/queries/useTogglePause";
2829

2930
import { ErrorAlert } from "../ErrorAlert";
3031
import { Checkbox } from "../ui/Checkbox";
@@ -38,6 +39,7 @@ const today = new Date().toISOString().slice(0, 16);
3839

3940
const RunBackfillForm = ({ dag, onClose }: RunBackfillFormProps) => {
4041
const [errors, setErrors] = useState<{ conf?: string; date?: unknown }>({});
42+
const [unpause, setUnpause] = useState(true);
4143

4244
const { control, handleSubmit, reset, watch } = useForm<BackfillPostBody>({
4345
defaultValues: {
@@ -69,6 +71,8 @@ const RunBackfillForm = ({ dag, onClose }: RunBackfillFormProps) => {
6971
},
7072
});
7173

74+
const { mutate: togglePause } = useTogglePause({ dagId: dag.dag_id });
75+
7276
const { createBackfill, dateValidationError, error, isPending } = useCreateBackfill({
7377
onSuccessConfirm: onClose,
7478
});
@@ -83,6 +87,14 @@ const RunBackfillForm = ({ dag, onClose }: RunBackfillFormProps) => {
8387
const dataIntervalEnd = watch("to_date");
8488

8589
const onSubmit = (fdata: BackfillPostBody) => {
90+
if (unpause && dag.is_paused) {
91+
togglePause({
92+
dagId: dag.dag_id,
93+
requestBody: {
94+
is_paused: false,
95+
},
96+
});
97+
}
8698
createBackfill({
8799
requestBody: fdata,
88100
});
@@ -195,6 +207,11 @@ const RunBackfillForm = ({ dag, onClose }: RunBackfillFormProps) => {
195207
<Alert>{affectedTasks.total_entries} runs will be triggered</Alert>
196208
) : undefined}
197209
</VStack>
210+
{dag.is_paused ? (
211+
<Checkbox checked={unpause} colorPalette="blue" onChange={() => setUnpause(!unpause)}>
212+
Unpause {dag.dag_display_name} on trigger
213+
</Checkbox>
214+
) : undefined}
198215
<ErrorAlert error={errors.date ?? error} />
199216
<Box as="footer" display="flex" justifyContent="flex-end" mt={4}>
200217
<HStack w="full">

airflow/ui/src/components/TogglePause.tsx

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,61 +17,35 @@
1717
* under the License.
1818
*/
1919
import { useDisclosure } from "@chakra-ui/react";
20-
import { useQueryClient } from "@tanstack/react-query";
2120
import { useCallback } from "react";
2221

23-
import {
24-
UseDagRunServiceGetDagRunsKeyFn,
25-
UseDagServiceGetDagDetailsKeyFn,
26-
UseDagServiceGetDagKeyFn,
27-
useDagServiceGetDagsKey,
28-
useDagServicePatchDag,
29-
useDagsServiceRecentDagRunsKey,
30-
UseTaskInstanceServiceGetTaskInstancesKeyFn,
31-
} from "openapi/queries";
3222
import { useConfig } from "src/queries/useConfig";
23+
import { useTogglePause } from "src/queries/useTogglePause";
3324

3425
import { ConfirmationModal } from "./ConfirmationModal";
3526
import { Switch, type SwitchProps } from "./ui";
3627

3728
type Props = {
3829
readonly dagDisplayName?: string;
3930
readonly dagId: string;
40-
readonly isPaused: boolean;
31+
readonly isPaused?: boolean;
4132
readonly skipConfirm?: boolean;
4233
} & SwitchProps;
4334

4435
export const TogglePause = ({ dagDisplayName, dagId, isPaused, skipConfirm, ...rest }: Props) => {
45-
const queryClient = useQueryClient();
4636
const { onClose, onOpen, open } = useDisclosure();
4737

48-
const onSuccess = async () => {
49-
const queryKeys = [
50-
[useDagServiceGetDagsKey],
51-
[useDagsServiceRecentDagRunsKey],
52-
UseDagServiceGetDagKeyFn({ dagId }, [{ dagId }]),
53-
UseDagServiceGetDagDetailsKeyFn({ dagId }, [{ dagId }]),
54-
UseDagRunServiceGetDagRunsKeyFn({ dagId }, [{ dagId }]),
55-
UseTaskInstanceServiceGetTaskInstancesKeyFn({ dagId, dagRunId: "~" }, [{ dagId, dagRunId: "~" }]),
56-
];
57-
58-
await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({ queryKey: key })));
59-
};
60-
61-
const { mutate } = useDagServicePatchDag({
62-
onSuccess,
63-
});
64-
38+
const { mutate: togglePause } = useTogglePause({ dagId });
6539
const showConfirmation = Boolean(useConfig("require_confirmation_dag_change"));
6640

6741
const onToggle = useCallback(() => {
68-
mutate({
42+
togglePause({
6943
dagId,
7044
requestBody: {
7145
is_paused: !isPaused,
7246
},
7347
});
74-
}, [dagId, isPaused, mutate]);
48+
}, [dagId, isPaused, togglePause]);
7549

7650
const onChange = () => {
7751
if (showConfirmation && skipConfirm !== true) {
@@ -83,7 +57,13 @@ export const TogglePause = ({ dagDisplayName, dagId, isPaused, skipConfirm, ...r
8357

8458
return (
8559
<>
86-
<Switch checked={!isPaused} colorPalette="blue" onCheckedChange={onChange} size="sm" {...rest} />
60+
<Switch
61+
checked={isPaused === undefined ? undefined : !isPaused}
62+
colorPalette="blue"
63+
onCheckedChange={onChange}
64+
size="sm"
65+
{...rest}
66+
/>
8767
<ConfirmationModal
8868
header={`${isPaused ? "Unpause" : "Pause"} ${dagDisplayName ?? dagId}?`}
8969
onConfirm={onToggle}

airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,20 @@ import { useForm, Controller } from "react-hook-form";
2222
import { FiPlay } from "react-icons/fi";
2323

2424
import { useDagParams } from "src/queries/useDagParams";
25+
import { useTogglePause } from "src/queries/useTogglePause";
2526
import { useTrigger } from "src/queries/useTrigger";
2627

2728
import { ErrorAlert } from "../ErrorAlert";
2829
import { FlexibleForm, flexibleFormDefaultSection } from "../FlexibleForm";
2930
import { JsonEditor } from "../JsonEditor";
3031
import { Accordion } from "../ui";
32+
import { Checkbox } from "../ui/Checkbox";
3133
import EditableMarkdown from "./EditableMarkdown";
3234
import { useParamStore } from "./useParamStore";
3335

3436
type TriggerDAGFormProps = {
3537
readonly dagId: string;
38+
readonly isPaused: boolean;
3639
readonly onClose: () => void;
3740
readonly open: boolean;
3841
};
@@ -44,11 +47,14 @@ export type DagRunTriggerParams = {
4447
note: string;
4548
};
4649

47-
const TriggerDAGForm = ({ dagId, onClose, open }: TriggerDAGFormProps) => {
50+
const TriggerDAGForm = ({ dagId, isPaused, onClose, open }: TriggerDAGFormProps) => {
4851
const [errors, setErrors] = useState<{ conf?: string; date?: unknown }>({});
4952
const initialParamsDict = useDagParams(dagId, open);
5053
const { error: errorTrigger, isPending, triggerDagRun } = useTrigger({ dagId, onSuccessConfirm: onClose });
5154
const { conf, setConf } = useParamStore();
55+
const [unpause, setUnpause] = useState(true);
56+
57+
const { mutate: togglePause } = useTogglePause({ dagId });
5258

5359
const { control, handleSubmit, reset } = useForm<DagRunTriggerParams>({
5460
defaultValues: {
@@ -67,6 +73,14 @@ const TriggerDAGForm = ({ dagId, onClose, open }: TriggerDAGFormProps) => {
6773
}, [conf, reset]);
6874

6975
const onSubmit = (data: DagRunTriggerParams) => {
76+
if (unpause && isPaused) {
77+
togglePause({
78+
dagId,
79+
requestBody: {
80+
is_paused: false,
81+
},
82+
});
83+
}
7084
triggerDagRun(data);
7185
};
7286

@@ -186,6 +200,11 @@ const TriggerDAGForm = ({ dagId, onClose, open }: TriggerDAGFormProps) => {
186200
</Accordion.ItemContent>
187201
</Accordion.Item>
188202
</Accordion.Root>
203+
{isPaused ? (
204+
<Checkbox checked={unpause} colorPalette="blue" onChange={() => setUnpause(!unpause)}>
205+
Unpause {dagId} on trigger
206+
</Checkbox>
207+
) : undefined}
189208
<ErrorAlert error={errors.date ?? errorTrigger} />
190209
<Box as="footer" display="flex" justifyContent="flex-end" mt={4}>
191210
<HStack w="full">

airflow/ui/src/components/TriggerDag/TriggerDAGModal.tsx

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@
1919
import { Heading, VStack } from "@chakra-ui/react";
2020
import React from "react";
2121

22-
import { Alert, Dialog } from "src/components/ui";
22+
import { Dialog } from "src/components/ui";
2323

24-
import { TogglePause } from "../TogglePause";
2524
import TriggerDAGForm from "./TriggerDAGForm";
2625

2726
type TriggerDAGModalProps = {
@@ -43,21 +42,14 @@ const TriggerDAGModal: React.FC<TriggerDAGModalProps> = ({
4342
<Dialog.Content backdrop>
4443
<Dialog.Header paddingBottom={0}>
4544
<VStack align="start" gap={4}>
46-
<Heading size="xl">
47-
Trigger Dag - {dagDisplayName} <TogglePause dagId={dagId} isPaused={isPaused} skipConfirm />
48-
</Heading>
49-
{isPaused ? (
50-
<Alert status="warning" title="Paused DAG">
51-
Triggering will create a DAG run, but it will not start until the Dag is unpaused.
52-
</Alert>
53-
) : undefined}
45+
<Heading size="xl">Trigger Dag - {dagDisplayName}</Heading>
5446
</VStack>
5547
</Dialog.Header>
5648

5749
<Dialog.CloseTrigger />
5850

5951
<Dialog.Body>
60-
<TriggerDAGForm dagId={dagId} onClose={onClose} open={open} />
52+
<TriggerDAGForm dagId={dagId} isPaused={isPaused} onClose={onClose} open={open} />
6153
</Dialog.Body>
6254
</Dialog.Content>
6355
</Dialog.Root>

airflow/ui/src/pages/Asset/CreateAssetEventModal.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
UseAssetServiceGetAssetEventsKeyFn,
2727
useAssetServiceMaterializeAsset,
2828
UseDagRunServiceGetDagRunsKeyFn,
29+
useDagServiceGetDagDetails,
2930
useDagsServiceRecentDagRunsKey,
3031
useDependenciesServiceGetDependencies,
3132
UseGridServiceGridDataKeyFn,
@@ -40,7 +41,9 @@ import type {
4041
import { ErrorAlert } from "src/components/ErrorAlert";
4142
import { JsonEditor } from "src/components/JsonEditor";
4243
import { Dialog, toaster } from "src/components/ui";
44+
import { Checkbox } from "src/components/ui/Checkbox";
4345
import { RadioCardItem, RadioCardRoot } from "src/components/ui/RadioCard";
46+
import { useTogglePause } from "src/queries/useTogglePause";
4447

4548
type Props = {
4649
readonly asset: AssetResponse;
@@ -51,6 +54,7 @@ type Props = {
5154
export const CreateAssetEventModal = ({ asset, onClose, open }: Props) => {
5255
const [eventType, setEventType] = useState("manual");
5356
const [extraError, setExtraError] = useState<string | undefined>();
57+
const [unpause, setUnpause] = useState(true);
5458
const [extra, setExtra] = useState("{}");
5559
const queryClient = useQueryClient();
5660

@@ -118,6 +122,12 @@ export const CreateAssetEventModal = ({ asset, onClose, open }: Props) => {
118122
await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({ queryKey: key })));
119123
};
120124

125+
const { data: dag } = useDagServiceGetDagDetails({ dagId: upstreamDagId ?? "" }, undefined, {
126+
enabled: Boolean(upstreamDagId),
127+
});
128+
129+
const { mutate: togglePause } = useTogglePause({ dagId: dag?.dag_id ?? upstreamDagId ?? "" });
130+
121131
const {
122132
error: manualError,
123133
isPending,
@@ -133,6 +143,14 @@ export const CreateAssetEventModal = ({ asset, onClose, open }: Props) => {
133143

134144
const handleSubmit = () => {
135145
if (eventType === "materialize") {
146+
if (unpause && dag?.is_paused) {
147+
togglePause({
148+
dagId: dag.dag_id,
149+
requestBody: {
150+
is_paused: false,
151+
},
152+
});
153+
}
136154
materializeAsset({ assetId: asset.id });
137155
} else {
138156
createAssetEvent({
@@ -162,7 +180,7 @@ export const CreateAssetEventModal = ({ asset, onClose, open }: Props) => {
162180
>
163181
<HStack align="stretch">
164182
<RadioCardItem
165-
description={`Trigger the Dag upstream of this asset${upstreamDagId === undefined ? "" : `: ${upstreamDagId}`}`}
183+
description={`Trigger the Dag upstream of this asset${upstreamDagId === undefined ? "" : `: ${dag?.dag_display_name ?? upstreamDagId}`}`}
166184
disabled={!hasUpstreamDag}
167185
label="Materialize"
168186
value="materialize"
@@ -177,6 +195,11 @@ export const CreateAssetEventModal = ({ asset, onClose, open }: Props) => {
177195
<Text color="fg.error">{extraError}</Text>
178196
</Field.Root>
179197
) : undefined}
198+
{eventType === "materialize" && dag?.is_paused ? (
199+
<Checkbox checked={unpause} colorPalette="blue" onChange={() => setUnpause(!unpause)}>
200+
Unpause {dag.dag_display_name} on trigger
201+
</Checkbox>
202+
) : undefined}
180203
<ErrorAlert error={eventType === "manual" ? manualError : materializeError} />
181204
</Dialog.Body>
182205
<Dialog.Footer>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*!
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
import { useQueryClient } from "@tanstack/react-query";
20+
21+
import {
22+
UseDagRunServiceGetDagRunsKeyFn,
23+
UseDagServiceGetDagDetailsKeyFn,
24+
UseDagServiceGetDagKeyFn,
25+
useDagServiceGetDagsKey,
26+
useDagServicePatchDag,
27+
useDagsServiceRecentDagRunsKey,
28+
UseTaskInstanceServiceGetTaskInstancesKeyFn,
29+
} from "openapi/queries";
30+
31+
export const useTogglePause = ({ dagId }: { dagId: string }) => {
32+
const queryClient = useQueryClient();
33+
34+
const onSuccess = async () => {
35+
const queryKeys = [
36+
[useDagServiceGetDagsKey],
37+
[useDagsServiceRecentDagRunsKey],
38+
UseDagServiceGetDagKeyFn({ dagId }, [{ dagId }]),
39+
UseDagServiceGetDagDetailsKeyFn({ dagId }, [{ dagId }]),
40+
UseDagRunServiceGetDagRunsKeyFn({ dagId }, [{ dagId }]),
41+
UseTaskInstanceServiceGetTaskInstancesKeyFn({ dagId, dagRunId: "~" }, [{ dagId, dagRunId: "~" }]),
42+
];
43+
44+
await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({ queryKey: key })));
45+
};
46+
47+
return useDagServicePatchDag({
48+
onSuccess,
49+
});
50+
};

0 commit comments

Comments
 (0)