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

pierrejeambrun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new fb320ab8a00 AIP-38 Default value for version selector (#47154)
fb320ab8a00 is described below

commit fb320ab8a009d1b89b96ac99d1628d9256f95deb
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Mon Mar 3 15:10:24 2025 +0100

    AIP-38 Default value for version selector (#47154)
    
    * AIP-38 Default value for version selector
    
    * Fix following code review
---
 airflow/ui/src/components/DagVersionSelect.tsx   | 26 ++++----
 airflow/ui/src/hooks/useSelectedVersion.ts       | 83 ++++++++++++++++++++++++
 airflow/ui/src/layouts/Details/DetailsLayout.tsx |  2 +-
 airflow/ui/src/layouts/Details/Graph/Graph.tsx   | 11 ++--
 airflow/ui/src/layouts/Details/PanelButtons.tsx  |  5 +-
 airflow/ui/src/pages/Dag/Code/Code.tsx           | 14 ++--
 6 files changed, 108 insertions(+), 33 deletions(-)

diff --git a/airflow/ui/src/components/DagVersionSelect.tsx 
b/airflow/ui/src/components/DagVersionSelect.tsx
index 9b3e3ea9a5a..2dbb6089561 100644
--- a/airflow/ui/src/components/DagVersionSelect.tsx
+++ b/airflow/ui/src/components/DagVersionSelect.tsx
@@ -21,25 +21,23 @@ import { useQueryClient } from "@tanstack/react-query";
 import type { OptionsOrGroups, GroupBase, SingleValue } from 
"chakra-react-select";
 import { AsyncSelect } from "chakra-react-select";
 import { useCallback } from "react";
-import { useSearchParams } from "react-router-dom";
+import { useParams, useSearchParams } from "react-router-dom";
 
 import { UseDagVersionServiceGetDagVersionsKeyFn } from "openapi/queries";
 import { DagVersionService } from "openapi/requests/services.gen";
 import type { DAGVersionCollectionResponse, DagVersionResponse } from 
"openapi/requests/types.gen";
 import { SearchParamsKeys } from "src/constants/searchParams";
+import useSelectedVersion from "src/hooks/useSelectedVersion";
 import type { Option } from "src/utils/option";
 
-const DagVersionSelect = ({
-  dagId,
-  disabled = false,
-}: {
-  readonly dagId: string | undefined;
-  readonly disabled?: boolean;
-}) => {
+const DagVersionSelect = ({ disabled = false }: { readonly disabled?: boolean 
}) => {
   const queryClient = useQueryClient();
 
   const [searchParams, setSearchParams] = useSearchParams();
-  const selectedVersion = searchParams.get(SearchParamsKeys.VERSION_NUMBER);
+
+  const selectedVersion = useSelectedVersion();
+
+  const { dagId = "" } = useParams();
 
   const loadVersions = (
     _: string,
@@ -48,7 +46,7 @@ const DagVersionSelect = ({
     queryClient.fetchQuery({
       queryFn: () =>
         DagVersionService.getDagVersions({
-          dagId: dagId ?? "",
+          dagId,
         }).then((data: DAGVersionCollectionResponse) => {
           const options = data.dag_versions.map((version: DagVersionResponse) 
=> {
             const versionNumber = version.version_number.toString();
@@ -63,7 +61,7 @@ const DagVersionSelect = ({
 
           return options;
         }),
-      queryKey: UseDagVersionServiceGetDagVersionsKeyFn({ dagId: dagId ?? "" 
}),
+      queryKey: UseDagVersionServiceGetDagVersionsKeyFn({ dagId }),
       staleTime: 0,
     });
 
@@ -86,9 +84,9 @@ const DagVersionSelect = ({
         loadOptions={loadVersions}
         onChange={handleStateChange}
         placeholder="Dag Version"
-        // null is required 
https://github.com/JedWatson/react-select/issues/3066
-        // eslint-disable-next-line unicorn/no-null
-        value={selectedVersion === null ? null : { label: 
`v${selectedVersion}`, value: selectedVersion }}
+        value={
+          selectedVersion === undefined ? undefined : { label: 
`v${selectedVersion}`, value: selectedVersion }
+        }
       />
     </Field.Root>
   );
diff --git a/airflow/ui/src/hooks/useSelectedVersion.ts 
b/airflow/ui/src/hooks/useSelectedVersion.ts
new file mode 100644
index 00000000000..01258a238ba
--- /dev/null
+++ b/airflow/ui/src/hooks/useSelectedVersion.ts
@@ -0,0 +1,83 @@
+/*!
+ * 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 { useParams, useSearchParams } from "react-router-dom";
+
+import {
+  useDagRunServiceGetDagRun,
+  useDagServiceGetDagDetails,
+  useTaskInstanceServiceGetMappedTaskInstance,
+  useTaskServiceGetTask,
+} from "openapi/queries";
+import { SearchParamsKeys } from "src/constants/searchParams";
+
+const useSelectedVersion = (): string | undefined => {
+  const [searchParams] = useSearchParams();
+
+  const selectedVersionUrl = searchParams.get(SearchParamsKeys.VERSION_NUMBER);
+
+  const { dagId = "", mapIndex = "-1", runId = "", taskId = "" } = useParams();
+
+  const { data: dagData } = useDagServiceGetDagDetails(
+    {
+      dagId,
+    },
+    undefined,
+    { enabled: Boolean(dagId) },
+  );
+
+  const { data: runData } = useDagRunServiceGetDagRun(
+    {
+      dagId,
+      dagRunId: runId,
+    },
+    undefined,
+    { enabled: Boolean(dagId) && Boolean(runId) },
+  );
+
+  const { data: taskData } = useTaskServiceGetTask(
+    {
+      dagId,
+      taskId,
+    },
+    undefined,
+    { enabled: Boolean(dagId) && Boolean(runId) && Boolean(taskId) },
+  );
+
+  const { data: mappedTaskInstanceData } = 
useTaskInstanceServiceGetMappedTaskInstance(
+    {
+      dagId,
+      dagRunId: runId,
+      mapIndex: parseInt(mapIndex, 10),
+      taskId,
+    },
+    undefined,
+    // Do not enable on a task instance summary. Mapped task but no mapIndex 
defined.
+    { enabled: taskData !== undefined && !Boolean(mapIndex === "-1" && 
taskData.is_mapped) },
+  );
+
+  const selectedVersionNumber =
+    selectedVersionUrl ??
+    mappedTaskInstanceData?.dag_version?.version_number ??
+    runData?.dag_versions.at(-1)?.version_number ??
+    dagData?.latest_dag_version?.version_number;
+
+  return selectedVersionNumber?.toString();
+};
+
+export default useSelectedVersion;
diff --git a/airflow/ui/src/layouts/Details/DetailsLayout.tsx 
b/airflow/ui/src/layouts/Details/DetailsLayout.tsx
index c93c8fe8b5f..cbe63d77d4a 100644
--- a/airflow/ui/src/layouts/Details/DetailsLayout.tsx
+++ b/airflow/ui/src/layouts/Details/DetailsLayout.tsx
@@ -71,7 +71,7 @@ export const DetailsLayout = ({ children, error, isLoading, 
tabs }: Props) => {
         <PanelGroup autoSaveId={dagId} direction="horizontal">
           <Panel defaultSize={20} minSize={6}>
             <Box height="100%" position="relative" pr={2}>
-              <PanelButtons dagId={dagId} dagView={dagView} 
setDagView={setDagView} />
+              <PanelButtons dagView={dagView} setDagView={setDagView} />
               {dagView === "graph" ? <Graph /> : <Grid />}
             </Box>
           </Panel>
diff --git a/airflow/ui/src/layouts/Details/Graph/Graph.tsx 
b/airflow/ui/src/layouts/Details/Graph/Graph.tsx
index 8b5b6c466e5..09c1e93e1de 100644
--- a/airflow/ui/src/layouts/Details/Graph/Graph.tsx
+++ b/airflow/ui/src/layouts/Details/Graph/Graph.tsx
@@ -19,12 +19,12 @@
 import { useToken } from "@chakra-ui/react";
 import { ReactFlow, Controls, Background, MiniMap, type Node as ReactFlowNode 
} from "@xyflow/react";
 import "@xyflow/react/dist/style.css";
-import { useParams, useSearchParams } from "react-router-dom";
+import { useParams } from "react-router-dom";
 
 import { useGridServiceGridData, useStructureServiceStructureData } from 
"openapi/queries";
-import { SearchParamsKeys } from "src/constants/searchParams";
 import { useColorMode } from "src/context/colorMode";
 import { useOpenGroups } from "src/context/openGroups";
+import useSelectedVersion from "src/hooks/useSelectedVersion";
 import { isStatePending, useAutoRefresh } from "src/utils";
 
 import Edge from "./Edge";
@@ -33,8 +33,6 @@ import { TaskNode } from "./TaskNode";
 import type { CustomNodeProps } from "./reactflowUtils";
 import { useGraphLayout } from "./useGraphLayout";
 
-const VERSION_NUMBER_PARAM = SearchParamsKeys.VERSION_NUMBER;
-
 const nodeColor = (
   { data: { depth, height, isOpen, taskInstance, width }, type }: 
ReactFlowNode<CustomNodeProps>,
   evenColor?: string,
@@ -67,8 +65,7 @@ export const Graph = () => {
   const { colorMode = "light" } = useColorMode();
   const { dagId = "", runId, taskId } = useParams();
 
-  const [searchParams] = useSearchParams();
-  const selectedVersion = searchParams.get(VERSION_NUMBER_PARAM);
+  const selectedVersion = useSelectedVersion();
 
   // corresponds to the "bg", "bg.emphasized", "border.inverted" semantic 
tokens
   const [oddLight, oddDark, evenLight, evenDark, selectedDarkColor, 
selectedLightColor] = useToken("colors", [
@@ -86,7 +83,7 @@ export const Graph = () => {
 
   const { data: graphData = { arrange: "LR", edges: [], nodes: [] } } = 
useStructureServiceStructureData({
     dagId,
-    versionNumber: selectedVersion === null ? undefined : 
parseInt(selectedVersion, 10),
+    versionNumber: selectedVersion === undefined ? undefined : 
parseInt(selectedVersion, 10),
   });
 
   const { data } = useGraphLayout({
diff --git a/airflow/ui/src/layouts/Details/PanelButtons.tsx 
b/airflow/ui/src/layouts/Details/PanelButtons.tsx
index 5898941ae12..30ad6b93c5a 100644
--- a/airflow/ui/src/layouts/Details/PanelButtons.tsx
+++ b/airflow/ui/src/layouts/Details/PanelButtons.tsx
@@ -23,12 +23,11 @@ import { MdOutlineAccountTree } from "react-icons/md";
 import DagVersionSelect from "src/components/DagVersionSelect";
 
 type Props = {
-  readonly dagId: string;
   readonly dagView: string;
   readonly setDagView: (x: "graph" | "grid") => void;
 } & StackProps;
 
-export const PanelButtons = ({ dagId, dagView, setDagView, ...rest }: Props) 
=> (
+export const PanelButtons = ({ dagView, setDagView, ...rest }: Props) => (
   <HStack justifyContent="space-between" position="absolute" top={0} 
width="100%" zIndex={1} {...rest}>
     <ButtonGroup attached size="sm" variant="outline">
       <IconButton
@@ -51,7 +50,7 @@ export const PanelButtons = ({ dagId, dagView, setDagView, 
...rest }: Props) =>
       </IconButton>
     </ButtonGroup>
     <Box bg="bg" mr={2}>
-      <DagVersionSelect dagId={dagId} disabled={dagView !== "graph"} />
+      <DagVersionSelect disabled={dagView !== "graph"} />
     </Box>
   </HStack>
 );
diff --git a/airflow/ui/src/pages/Dag/Code/Code.tsx 
b/airflow/ui/src/pages/Dag/Code/Code.tsx
index f32b31dbf47..1572403742d 100644
--- a/airflow/ui/src/pages/Dag/Code/Code.tsx
+++ b/airflow/ui/src/pages/Dag/Code/Code.tsx
@@ -18,7 +18,7 @@
  */
 import { Box, Button, Heading, HStack } from "@chakra-ui/react";
 import { useState } from "react";
-import { useParams, useSearchParams } from "react-router-dom";
+import { useParams } from "react-router-dom";
 import { createElement, PrismLight as SyntaxHighlighter } from 
"react-syntax-highlighter";
 import python from "react-syntax-highlighter/dist/esm/languages/prism/python";
 import { oneLight, oneDark } from 
"react-syntax-highlighter/dist/esm/styles/prism";
@@ -28,18 +28,16 @@ import DagVersionSelect from 
"src/components/DagVersionSelect";
 import { ErrorAlert } from "src/components/ErrorAlert";
 import Time from "src/components/Time";
 import { ProgressBar } from "src/components/ui";
-import { SearchParamsKeys } from "src/constants/searchParams";
 import { useColorMode } from "src/context/colorMode";
+import useSelectedVersion from "src/hooks/useSelectedVersion";
 import { useConfig } from "src/queries/useConfig";
 
 SyntaxHighlighter.registerLanguage("python", python);
 
-const VERSION_NUMBER_PARAM = SearchParamsKeys.VERSION_NUMBER;
-
 export const Code = () => {
   const { dagId } = useParams();
-  const [searchParams] = useSearchParams();
-  const selectedVersion = searchParams.get(VERSION_NUMBER_PARAM);
+
+  const selectedVersion = useSelectedVersion();
 
   const {
     data: dag,
@@ -55,7 +53,7 @@ export const Code = () => {
     isLoading: isCodeLoading,
   } = useDagSourceServiceGetDagSource({
     dagId: dagId ?? "",
-    versionNumber: selectedVersion === null ? undefined : 
parseInt(selectedVersion, 10),
+    versionNumber: selectedVersion === undefined ? undefined : 
parseInt(selectedVersion, 10),
   });
 
   const defaultWrap = Boolean(useConfig("default_wrap"));
@@ -81,7 +79,7 @@ export const Code = () => {
           </Heading>
         )}
         <HStack>
-          <DagVersionSelect dagId={dagId} />
+          <DagVersionSelect />
           <Button aria-label={wrap ? "Unwrap" : "Wrap"} bg="bg.panel" 
onClick={toggleWrap} variant="outline">
             {wrap ? "Unwrap" : "Wrap"}
           </Button>

Reply via email to