This is an automated email from the ASF dual-hosted git repository. kaxilnaik pushed a commit to branch v3-0-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 551a2326874c6504ac9b8538d0d7aaa051a659d5 Author: Brent Bovenzi <[email protected]> AuthorDate: Tue Apr 29 08:50:13 2025 -0400 Create a timezone-aware datetime input component (#49911) (cherry picked from commit 935c49d780ff3da349ba55289f949ecdecbbe21e) --- .../src/components/DagActions/RunBackfillForm.tsx | 7 ++- .../airflow/ui/src/components/DateTimeInput.tsx | 56 ++++++++++++++++++++++ .../src/components/FlexibleForm/FieldDateTime.tsx | 13 +++++ .../src/components/TriggerDag/TriggerDAGForm.tsx | 17 ++----- 4 files changed, 76 insertions(+), 17 deletions(-) diff --git a/airflow-core/src/airflow/ui/src/components/DagActions/RunBackfillForm.tsx b/airflow-core/src/airflow/ui/src/components/DagActions/RunBackfillForm.tsx index 544876efa2f..4ab35b2f466 100644 --- a/airflow-core/src/airflow/ui/src/components/DagActions/RunBackfillForm.tsx +++ b/airflow-core/src/airflow/ui/src/components/DagActions/RunBackfillForm.tsx @@ -28,6 +28,7 @@ import { useCreateBackfillDryRun } from "src/queries/useCreateBackfillDryRun"; import { useTogglePause } from "src/queries/useTogglePause"; import { pluralize } from "src/utils"; +import { DateTimeInput } from "../DateTimeInput"; import { ErrorAlert } from "../ErrorAlert"; import { Checkbox } from "../ui/Checkbox"; import { RadioCardItem, RadioCardLabel, RadioCardRoot } from "../ui/RadioCard"; @@ -143,12 +144,11 @@ const RunBackfillForm = ({ dag, onClose }: RunBackfillFormProps) => { render={({ field }) => ( <Field.Root invalid={Boolean(errors.date)}> <Field.Label>From</Field.Label> - <Input + <DateTimeInput {...field} max={dataIntervalEnd || today} onBlur={resetDateError} size="sm" - type="datetime-local" /> </Field.Root> )} @@ -159,13 +159,12 @@ const RunBackfillForm = ({ dag, onClose }: RunBackfillFormProps) => { render={({ field }) => ( <Field.Root invalid={Boolean(errors.date)}> <Field.Label>To</Field.Label> - <Input + <DateTimeInput {...field} max={today} min={dataIntervalStart || undefined} onBlur={resetDateError} size="sm" - type="datetime-local" /> </Field.Root> )} diff --git a/airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx b/airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx new file mode 100644 index 00000000000..c4fa9010fc2 --- /dev/null +++ b/airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx @@ -0,0 +1,56 @@ +/*! + * 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. + */ +import { Input, type InputProps } from "@chakra-ui/react"; +import dayjs from "dayjs"; +import tz from "dayjs/plugin/timezone"; +import { forwardRef } from "react"; + +import { useTimezone } from "src/context/timezone"; + +dayjs.extend(tz); + +type Props = { + readonly value: string; +} & InputProps; + +export const DateTimeInput = forwardRef<HTMLInputElement, Props>(({ onChange, value, ...rest }, ref) => { + const { selectedTimezone } = useTimezone(); + + // Make the value timezone-aware + const date = dayjs(value).tz(selectedTimezone).format("YYYY-MM-DDTHH:mm:ss.SSS"); + + return ( + <Input + onChange={(event) => + onChange?.({ + ...event, + target: { + ...event.target, + // Return a timezone-aware ISO string + value: dayjs(event.target.value).tz(selectedTimezone, true).toISOString(), + }, + }) + } + ref={ref} + type="datetime-local" + value={date} + {...rest} + /> + ); +}); diff --git a/airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldDateTime.tsx b/airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldDateTime.tsx index a5deae64b9a..07911837996 100644 --- a/airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldDateTime.tsx +++ b/airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldDateTime.tsx @@ -21,6 +21,7 @@ import { Input, type InputProps } from "@chakra-ui/react"; import { paramPlaceholder, useParamStore } from "src/queries/useParamStore"; import type { FlexibleFormElementProps } from "."; +import { DateTimeInput } from "../DateTimeInput"; export const FieldDateTime = ({ name, ...rest }: FlexibleFormElementProps & InputProps) => { const { paramsDict, setParamsDict } = useParamStore(); @@ -41,6 +42,18 @@ export const FieldDateTime = ({ name, ...rest }: FlexibleFormElementProps & Inpu setParamsDict(paramsDict); }; + if (rest.type === "datetime-local") { + return ( + <DateTimeInput + id={`element_${name}`} + name={`element_${name}`} + onChange={(event) => handleChange(event.target.value)} + size="sm" + value={((param.value ?? "") as string).slice(0, 16)} + /> + ); + } + return ( <Input id={`element_${name}`} diff --git a/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx b/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx index f73b8d5398d..b3d470ba417 100644 --- a/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx +++ b/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx @@ -18,18 +18,16 @@ */ import { Input, Button, Box, Spacer, HStack, Field, Stack } from "@chakra-ui/react"; import dayjs from "dayjs"; -import tz from "dayjs/plugin/timezone"; -import utc from "dayjs/plugin/utc"; import { useEffect, useState } from "react"; import { useForm, Controller } from "react-hook-form"; import { FiPlay } from "react-icons/fi"; -import { useTimezone } from "src/context/timezone"; import { useDagParams } from "src/queries/useDagParams"; import { useParamStore } from "src/queries/useParamStore"; import { useTogglePause } from "src/queries/useTogglePause"; import { useTrigger } from "src/queries/useTrigger"; +import { DateTimeInput } from "../DateTimeInput"; import { ErrorAlert } from "../ErrorAlert"; import { FlexibleForm, flexibleFormDefaultSection } from "../FlexibleForm"; import { JsonEditor } from "../JsonEditor"; @@ -37,9 +35,6 @@ import { Accordion } from "../ui"; import { Checkbox } from "../ui/Checkbox"; import EditableMarkdown from "./EditableMarkdown"; -dayjs.extend(utc); -dayjs.extend(tz); - type TriggerDAGFormProps = { readonly dagId: string; readonly isPaused: boolean; @@ -57,7 +52,6 @@ export type DagRunTriggerParams = { const TriggerDAGForm = ({ dagId, isPaused, onClose, open }: TriggerDAGFormProps) => { const [errors, setErrors] = useState<{ conf?: string; date?: unknown }>({}); const initialParamsDict = useDagParams(dagId, open); - const { selectedTimezone } = useTimezone(); const { error: errorTrigger, isPending, triggerDagRun } = useTrigger({ dagId, onSuccessConfirm: onClose }); const { conf, setConf } = useParamStore(); const [unpause, setUnpause] = useState(true); @@ -69,7 +63,7 @@ const TriggerDAGForm = ({ dagId, isPaused, onClose, open }: TriggerDAGFormProps) conf, dagRunId: "", // Default logical date to now, show it in the selected timezone - logicalDate: dayjs().tz(selectedTimezone).format("YYYY-MM-DDTHH:mm:ss.SSS"), + logicalDate: dayjs().format("YYYY-MM-DDTHH:mm:ss.SSS"), note: "", }, }); @@ -90,10 +84,7 @@ const TriggerDAGForm = ({ dagId, isPaused, onClose, open }: TriggerDAGFormProps) }, }); } - triggerDagRun({ - ...data, - logicalDate: dayjs(data.logicalDate).tz(selectedTimezone, true).toISOString(), - }); + triggerDagRun(data); }; const validateAndPrettifyJson = (value: string) => { @@ -154,7 +145,7 @@ const TriggerDAGForm = ({ dagId, isPaused, onClose, open }: TriggerDAGFormProps) </Field.Label> </Stack> <Stack css={{ flexBasis: "70%" }}> - <Input {...field} onBlur={resetDateError} size="sm" type="datetime-local" /> + <DateTimeInput {...field} onBlur={resetDateError} size="sm" /> </Stack> </Field.Root> )}
