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 393a58451d7 Add Dag Page header (#43362)
393a58451d7 is described below

commit 393a58451d7b2f6f3d5b3ab853486cb7900ae5b1
Author: Brent Bovenzi <[email protected]>
AuthorDate: Mon Oct 28 04:41:13 2024 -0400

    Add Dag Page header (#43362)
    
    * Add ability to select a dag from dags list
    
    * Fix event propagation and onRowClicked type
    
    * Remove div links and only show error stack trace in dev
    
    * use get dag details to build header for Dag Page
    
    * Rebase fix and move semantic color gen to a function
---
 airflow/ui/src/pages/DagsList/Dag/Dag.tsx      |  69 +++++++++++++-
 airflow/ui/src/pages/DagsList/Dag/Header.tsx   | 120 +++++++++++++++++++++++++
 airflow/ui/src/pages/DagsList/DagCard.test.tsx |   2 +-
 airflow/ui/src/pages/DagsList/DagCard.tsx      |  50 ++++-------
 airflow/ui/src/pages/DagsList/DagTags.tsx      |  55 ++++++++++++
 airflow/ui/src/pages/DagsList/DagsFilters.tsx  |  88 ++++++++----------
 airflow/ui/src/theme.ts                        |  27 +++---
 7 files changed, 311 insertions(+), 100 deletions(-)

diff --git a/airflow/ui/src/pages/DagsList/Dag/Dag.tsx 
b/airflow/ui/src/pages/DagsList/Dag/Dag.tsx
index 79290803e70..52f8934e899 100644
--- a/airflow/ui/src/pages/DagsList/Dag/Dag.tsx
+++ b/airflow/ui/src/pages/DagsList/Dag/Dag.tsx
@@ -16,11 +16,72 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { Box } from "@chakra-ui/react";
-import { useParams } from "react-router-dom";
+import {
+  Box,
+  Button,
+  Progress,
+  Tab,
+  TabList,
+  TabPanel,
+  TabPanels,
+  Tabs,
+} from "@chakra-ui/react";
+import { FiChevronsLeft } from "react-icons/fi";
+import { Link as RouterLink, useParams } from "react-router-dom";
+
+import { useDagServiceGetDagDetails } from "openapi/queries";
+import { ErrorAlert } from "src/components/ErrorAlert";
+
+import { Header } from "./Header";
 
 export const Dag = () => {
-  const params = useParams();
+  const { dagId } = useParams();
+
+  const {
+    data: dag,
+    error,
+    isLoading,
+  } = useDagServiceGetDagDetails({
+    dagId: dagId ?? "",
+  });
+
+  return (
+    <Box>
+      <Button
+        as={RouterLink}
+        color="blue.400"
+        leftIcon={<FiChevronsLeft />}
+        to="/dags"
+        variant="link"
+      >
+        Back to all dags
+      </Button>
+      <Header dag={dag} dagId={dagId} />
+      <ErrorAlert error={error} />
+      <Progress
+        isIndeterminate
+        size="xs"
+        visibility={isLoading ? "visible" : "hidden"}
+      />
+      <Tabs>
+        <TabList>
+          <Tab>Overview</Tab>
+          <Tab>Runs</Tab>
+          <Tab>Tasks</Tab>
+        </TabList>
 
-  return <Box>{params.dagId}</Box>;
+        <TabPanels>
+          <TabPanel>
+            <p>one!</p>
+          </TabPanel>
+          <TabPanel>
+            <p>two!</p>
+          </TabPanel>
+          <TabPanel>
+            <p>three!</p>
+          </TabPanel>
+        </TabPanels>
+      </Tabs>
+    </Box>
+  );
 };
diff --git a/airflow/ui/src/pages/DagsList/Dag/Header.tsx 
b/airflow/ui/src/pages/DagsList/Dag/Header.tsx
new file mode 100644
index 00000000000..25d674e1441
--- /dev/null
+++ b/airflow/ui/src/pages/DagsList/Dag/Header.tsx
@@ -0,0 +1,120 @@
+/*!
+ * 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 {
+  Box,
+  Button,
+  Flex,
+  Heading,
+  HStack,
+  SimpleGrid,
+  Text,
+  Tooltip,
+  useColorModeValue,
+  VStack,
+} from "@chakra-ui/react";
+import { FiCalendar, FiPlay } from "react-icons/fi";
+
+import type { DAGResponse } from "openapi/requests/types.gen";
+import { DagIcon } from "src/assets/DagIcon";
+import Time from "src/components/Time";
+import { TogglePause } from "src/components/TogglePause";
+
+import { DagTags } from "../DagTags";
+
+export const Header = ({
+  dag,
+  dagId,
+}: {
+  readonly dag?: DAGResponse;
+  readonly dagId?: string;
+}) => {
+  const grayBg = useColorModeValue("gray.100", "gray.900");
+  const grayBorder = useColorModeValue("gray.200", "gray.700");
+
+  return (
+    <Box
+      borderColor={grayBorder}
+      borderRadius={8}
+      borderWidth={1}
+      overflow="hidden"
+    >
+      <Box p={2}>
+        <Flex alignItems="center" justifyContent="space-between">
+          <HStack alignItems="center" spacing={2}>
+            <DagIcon height={8} width={8} />
+            <Heading size="lg">{dag?.dag_display_name ?? dagId}</Heading>
+            {dag !== undefined && (
+              <TogglePause dagId={dag.dag_id} isPaused={dag.is_paused} />
+            )}
+          </HStack>
+          <Flex>
+            <Button colorScheme="blue" isDisabled leftIcon={<FiPlay />}>
+              Trigger
+            </Button>
+          </Flex>
+        </Flex>
+        <SimpleGrid columns={4} height={8} my={4} spacing={4}>
+          <VStack align="flex-start" spacing={1}>
+            <Heading color="gray.500" fontSize="xs">
+              Last Run
+            </Heading>
+          </VStack>
+          <VStack align="flex-start" spacing={1}>
+            <Heading color="gray.500" fontSize="xs">
+              Next Run
+            </Heading>
+            {Boolean(dag?.next_dagrun) ? (
+              <Text fontSize="sm">
+                <Time datetime={dag?.next_dagrun} />
+              </Text>
+            ) : undefined}
+          </VStack>
+          <VStack align="flex-start" spacing={1}>
+            <Heading color="gray.500" fontSize="xs">
+              Schedule
+            </Heading>
+            {Boolean(dag?.timetable_summary) ? (
+              <Tooltip hasArrow label={dag?.timetable_description}>
+                <Text fontSize="sm">
+                  <FiCalendar style={{ display: "inline" }} />{" "}
+                  {dag?.timetable_summary}
+                </Text>
+              </Tooltip>
+            ) : undefined}
+          </VStack>
+          <div />
+        </SimpleGrid>
+      </Box>
+      <Flex
+        alignItems="center"
+        bg={grayBg}
+        borderTopColor={grayBorder}
+        borderTopWidth={1}
+        color="gray.400"
+        fontSize="sm"
+        justifyContent="space-between"
+        px={2}
+        py={1}
+      >
+        <Text>Owner: {dag?.owners.join(", ")}</Text>
+        <DagTags tags={dag?.tags ?? []} />
+      </Flex>
+    </Box>
+  );
+};
diff --git a/airflow/ui/src/pages/DagsList/DagCard.test.tsx 
b/airflow/ui/src/pages/DagsList/DagCard.test.tsx
index 3ae6e4fceea..b039d7c90b4 100644
--- a/airflow/ui/src/pages/DagsList/DagCard.test.tsx
+++ b/airflow/ui/src/pages/DagsList/DagCard.test.tsx
@@ -82,6 +82,6 @@ describe("DagCard", () => {
 
     render(<DagCard dag={expandedMockDag} />, { wrapper: Wrapper });
     expect(screen.getByTestId("dag-tag")).toBeInTheDocument();
-    expect(screen.getByText("+1 more")).toBeInTheDocument();
+    expect(screen.getByText(", +1 more")).toBeInTheDocument();
   });
 });
diff --git a/airflow/ui/src/pages/DagsList/DagCard.tsx 
b/airflow/ui/src/pages/DagsList/DagCard.tsx
index c222ac67897..e3b933c8ebb 100644
--- a/airflow/ui/src/pages/DagsList/DagCard.tsx
+++ b/airflow/ui/src/pages/DagsList/DagCard.tsx
@@ -17,7 +17,6 @@
  * under the License.
  */
 import {
-  Badge,
   Box,
   Flex,
   HStack,
@@ -28,22 +27,22 @@ import {
   VStack,
   Link,
 } from "@chakra-ui/react";
-import { FiCalendar, FiTag } from "react-icons/fi";
+import { FiCalendar } from "react-icons/fi";
 import { Link as RouterLink } from "react-router-dom";
 
 import type { DAGResponse } from "openapi/requests/types.gen";
 import Time from "src/components/Time";
 import { TogglePause } from "src/components/TogglePause";
 
+import { DagTags } from "./DagTags";
+
 type Props = {
   readonly dag: DAGResponse;
 };
 
-const MAX_TAGS = 3;
-
 export const DagCard = ({ dag }: Props) => (
   <Box
-    borderColor="blue.minimal"
+    borderColor="gray.emphasized"
     borderRadius={8}
     borderWidth={1}
     overflow="hidden"
@@ -67,42 +66,26 @@ export const DagCard = ({ dag }: Props) => (
             {dag.dag_display_name}
           </Link>
         </Tooltip>
-        {dag.tags.length ? (
-          <HStack spacing={1}>
-            <FiTag data-testid="dag-tag" />
-            {dag.tags.slice(0, MAX_TAGS).map((tag) => (
-              <Badge key={tag.name}>{tag.name}</Badge>
-            ))}
-            {dag.tags.length > MAX_TAGS && (
-              <Tooltip
-                hasArrow
-                label={
-                  <VStack p={1} spacing={1}>
-                    {dag.tags.slice(MAX_TAGS).map((tag) => (
-                      <Badge key={tag.name}>{tag.name}</Badge>
-                    ))}
-                  </VStack>
-                }
-              >
-                <Badge>+{dag.tags.length - MAX_TAGS} more</Badge>
-              </Tooltip>
-            )}
-          </HStack>
-        ) : undefined}
+        <DagTags tags={dag.tags} />
+      </HStack>
+      <HStack>
+        <TogglePause dagId={dag.dag_id} isPaused={dag.is_paused} />
       </HStack>
-      <TogglePause dagId={dag.dag_id} isPaused={dag.is_paused} />
     </Flex>
     <SimpleGrid columns={4} height={20} px={3} py={2} spacing={4}>
       <div />
+      <VStack align="flex-start" spacing={1}>
+        <Heading color="gray.500" fontSize="xs">
+          Last Run
+        </Heading>
+      </VStack>
       <VStack align="flex-start" spacing={1}>
         <Heading color="gray.500" fontSize="xs">
           Next Run
         </Heading>
-        {Boolean(dag.next_dagrun) ? (
-          <Text fontSize="sm">
-            <Time datetime={dag.next_dagrun} />
-          </Text>
-        ) : undefined}
+        <Text fontSize="sm">
+          <Time datetime={dag.next_dagrun} />
+        </Text>
         {Boolean(dag.timetable_summary) ? (
           <Tooltip hasArrow label={dag.timetable_description}>
             <Text fontSize="sm">
@@ -114,7 +97,6 @@ export const DagCard = ({ dag }: Props) => (
         ) : undefined}
       </VStack>
       <div />
-      <div />
     </SimpleGrid>
   </Box>
 );
diff --git a/airflow/ui/src/pages/DagsList/DagTags.tsx 
b/airflow/ui/src/pages/DagsList/DagTags.tsx
new file mode 100644
index 00000000000..2a3cb3f76be
--- /dev/null
+++ b/airflow/ui/src/pages/DagsList/DagTags.tsx
@@ -0,0 +1,55 @@
+/*!
+ * 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 { Flex, Text, Tooltip, VStack } from "@chakra-ui/react";
+import { FiTag } from "react-icons/fi";
+
+import type { DagTagPydantic } from "openapi/requests/types.gen";
+
+const MAX_TAGS = 3;
+
+type Props = {
+  readonly tags: Array<DagTagPydantic>;
+};
+
+export const DagTags = ({ tags }: Props) =>
+  tags.length ? (
+    <Flex alignItems="center" ml={2}>
+      <FiTag data-testid="dag-tag" />
+      <Text ml={1}>
+        {tags
+          .slice(0, MAX_TAGS)
+          .map(({ name }) => name)
+          .join(", ")}
+      </Text>
+      {tags.length > MAX_TAGS && (
+        <Tooltip
+          hasArrow
+          label={
+            <VStack p={1} spacing={1}>
+              {tags.slice(MAX_TAGS).map((tag) => (
+                <Text key={tag.name}>{tag.name}</Text>
+              ))}
+            </VStack>
+          }
+        >
+          <Text>, +{tags.length - MAX_TAGS} more</Text>
+        </Tooltip>
+      )}
+    </Flex>
+  ) : undefined;
diff --git a/airflow/ui/src/pages/DagsList/DagsFilters.tsx 
b/airflow/ui/src/pages/DagsList/DagsFilters.tsx
index 0b2ff79fb0e..376b9220d23 100644
--- a/airflow/ui/src/pages/DagsList/DagsFilters.tsx
+++ b/airflow/ui/src/pages/DagsList/DagsFilters.tsx
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { HStack, Select, Text, Box } from "@chakra-ui/react";
+import { HStack, Select } from "@chakra-ui/react";
 import { Select as ReactSelect } from "chakra-react-select";
 import type { MultiValue } from "chakra-react-select";
 import { useCallback } from "react";
@@ -106,55 +106,45 @@ export const DagsFilters = () => {
   return (
     <HStack justifyContent="space-between">
       <HStack spacing={4}>
-        <Box>
-          <Text fontSize="sm" fontWeight={200} mb={1}>
-            State:
-          </Text>
-          <HStack>
-            <QuickFilterButton
-              isActive={isAll}
-              onClick={handleStateChange}
-              value="all"
-            >
-              All
-            </QuickFilterButton>
-            <QuickFilterButton
-              isActive={isFailed}
-              onClick={handleStateChange}
-              value="failed"
-            >
-              Failed
-            </QuickFilterButton>
-            <QuickFilterButton
-              isActive={isRunning}
-              onClick={handleStateChange}
-              value="running"
-            >
-              Running
-            </QuickFilterButton>
-            <QuickFilterButton
-              isActive={isSuccess}
-              onClick={handleStateChange}
-              value="success"
-            >
-              Successful
-            </QuickFilterButton>
-          </HStack>
-        </Box>
-        <Box>
-          <Text fontSize="sm" fontWeight={200} mb={1}>
-            Active:
-          </Text>
-          <Select
-            onChange={handlePausedChange}
-            value={showPaused ?? undefined}
-            variant="flushed"
+        <HStack>
+          <QuickFilterButton
+            isActive={isAll}
+            onClick={handleStateChange}
+            value="all"
           >
-            <option>All</option>
-            <option value="false">Enabled DAGs</option>
-            <option value="true">Disabled DAGs</option>
-          </Select>
-        </Box>
+            All
+          </QuickFilterButton>
+          <QuickFilterButton
+            isActive={isFailed}
+            onClick={handleStateChange}
+            value="failed"
+          >
+            Failed
+          </QuickFilterButton>
+          <QuickFilterButton
+            isActive={isRunning}
+            onClick={handleStateChange}
+            value="running"
+          >
+            Running
+          </QuickFilterButton>
+          <QuickFilterButton
+            isActive={isSuccess}
+            onClick={handleStateChange}
+            value="success"
+          >
+            Successful
+          </QuickFilterButton>
+        </HStack>
+        <Select
+          onChange={handlePausedChange}
+          value={showPaused ?? undefined}
+          variant="flushed"
+        >
+          <option>All</option>
+          <option value="false">Enabled DAGs</option>
+          <option value="true">Disabled DAGs</option>
+        </Select>
       </HStack>
       <ReactSelect
         aria-label="Filter Dags by tag"
diff --git a/airflow/ui/src/theme.ts b/airflow/ui/src/theme.ts
index 08e300a9bb4..ea6bc397940 100644
--- a/airflow/ui/src/theme.ts
+++ b/airflow/ui/src/theme.ts
@@ -52,6 +52,19 @@ const baseStyle = definePartsStyle(() => ({
 
 export const tableTheme = defineMultiStyleConfig({ baseStyle });
 
+const generateSemanticColors = (color: string) => ({
+  /* eslint-disable perfectionist/sort-objects */
+  contrast: { _dark: `${color}.200`, _light: `${color}.600` },
+  focusRing: `${color}.500`,
+  fg: { _dark: `${color}.600`, _light: `${color}.400` },
+  emphasized: { _dark: `${color}.700`, _light: `${color}.300` },
+  solid: { _dark: `${color}.800`, _light: `${color}.200` },
+  muted: { _dark: `${color}.900`, _light: `${color}.100` },
+  subtle: { _dark: `${color}.950`, _light: `${color}.50` },
+  minimal: { _dark: "gray.900", _light: `${color}.50` },
+  /* eslint-enable perfectionist/sort-objects */
+});
+
 const theme = extendTheme({
   colors: {
     blue: {
@@ -67,18 +80,8 @@ const theme = extendTheme({
   },
   semanticTokens: {
     colors: {
-      blue: {
-        /* eslint-disable perfectionist/sort-objects */
-        contrast: { _dark: "blue.200", _light: "blue.600" },
-        focusRing: "blue.500",
-        fg: { _dark: "blue.600", _light: "blue.400" },
-        emphasized: { _dark: "blue.700", _light: "blue.300" },
-        solid: { _dark: "blue.800", _light: "blue.200" },
-        muted: { _dark: "blue.900", _light: "blue.100" },
-        subtle: { _dark: "blue.950", _light: "blue.50" },
-        minimal: { _dark: "gray.900", _light: "blue.50" },
-        /* eslint-enable perfectionist/sort-objects */
-      },
+      blue: generateSemanticColors("blue"),
+      gray: generateSemanticColors("gray"),
     },
   },
   styles: {

Reply via email to