This is an automated email from the ASF dual-hosted git repository.

vatsrahul1001 pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-4-test by this push:
     new 3bfd7728e36 [v3-4-test] UI: Guard the remaining RadioCardRoot values 
instead of casting (#74743) (#74989)
3bfd7728e36 is described below

commit 3bfd7728e36020c0d835bf84546f66c2bb5e3ee3
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sun Oct 18 17:03:07 2026 +0530

    [v3-4-test] UI: Guard the remaining RadioCardRoot values instead of casting 
(#74743) (#74989)
    
    The RadioCardRoot sites converted in #73985 left three behind. Two pass
    react-hook-form's field.onChange straight to onChange, which works only
    because react-hook-form reads target.value off the event, and the third
    casts the callback's value to its union. Both forms bypass the checks
    that the rest of the radio groups now get.
    
    Each of these groups was also seeded with defaultValue while mirroring
    the value into state, so the group and the state could disagree if a
    value were ever rejected; they are controlled now.
    
    The ReprocessBehavior values are shared between the backfill form and
    the backfill list, so the guard lives beside the options list it is
    derived from and cannot drift from the values actually rendered.
    (cherry picked from commit e2237bfc7ee7636e9d68df2958873fee1a425733)
    
    Co-authored-by: Brent Bovenzi <[email protected]>
---
 .../ui/src/components/DagActions/RunBackfillForm.tsx     | 11 +++++++++--
 .../ui/src/components/TriggerDag/TriggerDAGForm.tsx      | 16 ++++++++++++++--
 .../src/airflow/ui/src/components/TriggerDag/types.ts    |  5 ++++-
 .../airflow/ui/src/constants/reprocessBehaviourParams.ts |  8 +++++++-
 .../src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx | 11 +++--------
 .../ui/src/pages/TaskStateStore/TaskStateStoreModal.tsx  |  9 ++++++++-
 6 files changed, 45 insertions(+), 15 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 8834d2c9f15..ffc8fc8540f 100644
--- a/airflow-core/src/airflow/ui/src/components/DagActions/RunBackfillForm.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DagActions/RunBackfillForm.tsx
@@ -30,7 +30,7 @@ import { Alert, Checkbox, RadioCardItem, RadioCardLabel, 
RadioCardRoot } from "s
 
 import { useRerunWithLatestVersion } from 
"src/components/Clear/useRerunWithLatestVersion";
 
-import { reprocessBehaviors } from "src/constants/reprocessBehaviourParams";
+import { isReprocessBehavior, reprocessBehaviors } from 
"src/constants/reprocessBehaviourParams";
 import { useCreateBackfill } from "src/queries/useCreateBackfill";
 import { useCreateBackfillDryRun } from "src/queries/useCreateBackfillDryRun";
 import { useDagParams } from "src/queries/useDagParams";
@@ -204,7 +204,14 @@ const RunBackfillForm = ({ dag, disabled = false, onClose 
}: RunBackfillFormProp
           control={control}
           name="reprocess_behavior"
           render={({ field }) => (
-            <RadioCardRoot defaultValue={field.value} 
onChange={field.onChange}>
+            <RadioCardRoot
+              onValueChange={(details) => {
+                if (isReprocessBehavior(details.value)) {
+                  field.onChange(details.value);
+                }
+              }}
+              value={field.value}
+            >
               <RadioCardLabel fontSize="md" fontWeight="semibold" mb={3}>
                 {translate("backfill.reprocessBehavior")}
               </RadioCardLabel>
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 e1a7487026e..5f40f57f485 100644
--- a/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx
+++ b/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx
@@ -37,7 +37,12 @@ import { ErrorAlert, type ExpandedApiError } from 
"../ErrorAlert";
 import PausedDagOptions from "./PausedDagOptions";
 import RecentConfigSelect from "./RecentConfigSelect";
 import TriggerDAGAdvancedOptions from "./TriggerDAGAdvancedOptions";
-import { dataIntervalModeOptions, type DagRunTriggerParams, type 
PausedDagAction } from "./types";
+import {
+  dataIntervalModeOptions,
+  isDataIntervalMode,
+  type DagRunTriggerParams,
+  type PausedDagAction,
+} from "./types";
 
 type TriggerDAGFormProps = {
   readonly dagId: string;
@@ -209,7 +214,14 @@ const TriggerDAGForm = ({
                   control={control}
                   name="dataIntervalMode"
                   render={({ field }) => (
-                    <RadioCardRoot defaultValue={String(field.value)} 
onChange={field.onChange}>
+                    <RadioCardRoot
+                      onValueChange={(details) => {
+                        if (isDataIntervalMode(details.value)) {
+                          field.onChange(details.value);
+                        }
+                      }}
+                      value={field.value}
+                    >
                       <HStack align="stretch">
                         {dataIntervalModeOptions.map((mode) => (
                           <RadioCardItem
diff --git a/airflow-core/src/airflow/ui/src/components/TriggerDag/types.ts 
b/airflow-core/src/airflow/ui/src/components/TriggerDag/types.ts
index aa0b89c6aba..9040e701802 100644
--- a/airflow-core/src/airflow/ui/src/components/TriggerDag/types.ts
+++ b/airflow-core/src/airflow/ui/src/components/TriggerDag/types.ts
@@ -16,6 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { isOneOf } from "src/utils";
 
 export type DataIntervalMode = "auto" | "manual";
 
@@ -36,4 +37,6 @@ export type DagRunTriggerParams = {
 export const dataIntervalModeOptions = [
   { label: "components:triggerDag.dataIntervalAuto", value: "auto" },
   { label: "components:triggerDag.dataIntervalManual", value: "manual" },
-] as const satisfies Array<{ label: string; value: DataIntervalMode }>;
+] as const satisfies ReadonlyArray<{ label: string; value: DataIntervalMode }>;
+
+export const isDataIntervalMode = isOneOf(dataIntervalModeOptions.map((option) 
=> option.value));
diff --git 
a/airflow-core/src/airflow/ui/src/constants/reprocessBehaviourParams.ts 
b/airflow-core/src/airflow/ui/src/constants/reprocessBehaviourParams.ts
index 957f22050d8..65e553b79e0 100644
--- a/airflow-core/src/airflow/ui/src/constants/reprocessBehaviourParams.ts
+++ b/airflow-core/src/airflow/ui/src/constants/reprocessBehaviourParams.ts
@@ -16,8 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import type { ReprocessBehavior } from "openapi/requests/types.gen";
+
+import { isOneOf } from "src/utils";
+
 export const reprocessBehaviors = [
   { label: "components:backfill.missingRuns", value: "none" },
   { label: "components:backfill.missingAndErroredRuns", value: "failed" },
   { label: "components:backfill.allRuns", value: "completed" },
-];
+] as const satisfies ReadonlyArray<{ label: string; value: ReprocessBehavior 
}>;
+
+export const isReprocessBehavior = isOneOf(reprocessBehaviors.map((behavior) 
=> behavior.value));
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx
index 398394ebee2..3ba435b104e 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Backfills/Backfills.tsx
@@ -23,7 +23,7 @@ import { useTranslation } from "react-i18next";
 import { useLocation, useNavigate, useParams, useSearchParams } from 
"react-router-dom";
 
 import { useBackfillServiceListBackfillsUi } from "openapi/queries";
-import type { BackfillResponse, ReprocessBehavior } from 
"openapi/requests/types.gen";
+import type { BackfillResponse } from "openapi/requests/types.gen";
 
 import { DataTable } from "src/components/DataTable";
 import type { DataTableFeatures } from "src/components/DataTable/features";
@@ -31,8 +31,9 @@ import { useTableURLState } from 
"src/components/DataTable/useTableUrlState";
 import { ErrorAlert } from "src/components/ErrorAlert";
 import Time from "src/components/Time";
 
+import { isReprocessBehavior } from "src/constants/reprocessBehaviourParams";
 import { SearchParamsKeys, type SearchParamsKeysType } from 
"src/constants/searchParams";
-import { type DurationFormat, isOneOf, useDurationFormat } from "src/utils";
+import { type DurationFormat, useDurationFormat } from "src/utils";
 
 import { BackfillDagRunsModal } from "./BackfillDagRunsModal";
 import { BackfillsFilters } from "./BackfillsFilters";
@@ -53,12 +54,6 @@ const {
   TO_DATE_LTE: TO_DATE_LTE_PARAM,
 }: SearchParamsKeysType = SearchParamsKeys;
 
-const isReprocessBehavior = isOneOf([
-  "failed",
-  "completed",
-  "none",
-] as const satisfies ReadonlyArray<ReprocessBehavior>);
-
 type ColumnProps = {
   readonly onSelectBackfill: (backfillId: number) => void;
   readonly translate: TFunction;
diff --git 
a/airflow-core/src/airflow/ui/src/pages/TaskStateStore/TaskStateStoreModal.tsx 
b/airflow-core/src/airflow/ui/src/pages/TaskStateStore/TaskStateStoreModal.tsx
index 60ad38c2028..03946bf0b09 100644
--- 
a/airflow-core/src/airflow/ui/src/pages/TaskStateStore/TaskStateStoreModal.tsx
+++ 
b/airflow-core/src/airflow/ui/src/pages/TaskStateStore/TaskStateStoreModal.tsx
@@ -38,6 +38,9 @@ import { JsonEditor } from "src/components/JsonEditor";
 import { useTimezone } from "src/context/timezone";
 import { useTaskInstanceCoordinates } from 
"src/hooks/useTaskInstanceCoordinates";
 import { useStoreMutation } from "src/queries/useStoreMutation";
+import { isOneOf } from "src/utils";
+
+const isExpiresAtMode = isOneOf(["custom", "default", "never"] as const);
 
 dayjs.extend(tz);
 
@@ -195,7 +198,11 @@ export const TaskStateStoreModal = ({
               {translate("dag:taskStateStore.expiresAt.label")}
             </Text>
             <RadioCard.Root
-              onValueChange={(ev) => setExpiresAt(ev.value as "custom" | 
"default" | "never")}
+              onValueChange={(details) => {
+                if (isExpiresAtMode(details.value)) {
+                  setExpiresAt(details.value);
+                }
+              }}
               value={expiresAt}
             >
               <Flex gap={2}>

Reply via email to