diff --git a/airflow/ui/rules/react.js b/airflow/ui/rules/react.js index 508718659b4a..f5a54d05de7e 100644 --- a/airflow/ui/rules/react.js +++ b/airflow/ui/rules/react.js @@ -439,17 +439,6 @@ export const reactRules = /** @type {const} @satisfies {FlatConfig.Config} */ ({ */ [`${reactNamespace}/iframe-missing-sandbox`]: ERROR, - /** - * Enforce boolean attributes notation in JSX to never set it explicitly. - * - * @see [react/jsx-boolean-value](https://github.com/jsx-eslint/eslint-plugin-react/blob/HEAD/docs/rules/jsx-boolean-value.md) - */ - [`${reactNamespace}/jsx-boolean-value`]: [ - ERROR, - "never", - { assumeUndefinedIsFalse: true }, - ], - /** * Enforce curly braces or braces in JSX props and/or children. * diff --git a/airflow/ui/src/components/DagRunInfo.tsx b/airflow/ui/src/components/DagRunInfo.tsx new file mode 100644 index 000000000000..ecb062b8076a --- /dev/null +++ b/airflow/ui/src/components/DagRunInfo.tsx @@ -0,0 +1,80 @@ +/*! + * 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 { VStack, Text } from "@chakra-ui/react"; +import dayjs from "dayjs"; + +import Time from "src/components/Time"; +import { Tooltip } from "src/components/ui"; + +type Props = { + readonly dataIntervalEnd?: string | null; + readonly dataIntervalStart?: string | null; + readonly endDate?: string | null; + readonly logicalDate?: string | null; + readonly nextDagrunCreateAfter?: string | null; + readonly startDate?: string | null; +}; + +const DagRunInfo = ({ + dataIntervalEnd, + dataIntervalStart, + endDate, + logicalDate, + nextDagrunCreateAfter, + startDate, +}: Props) => + Boolean(dataIntervalStart) && Boolean(dataIntervalEnd) ? ( + + + Data Interval Start: + + Data Interval End: + {Boolean(nextDagrunCreateAfter) ? ( + + Run After: + ) : undefined} + {Boolean(logicalDate) ? ( + Logical Date: {logicalDate} + ) : undefined} + {Boolean(startDate) ? ( + Start Date: {startDate} + ) : undefined} + {Boolean(endDate) ? End Date: {endDate} : undefined} + {Boolean(startDate) && Boolean(endDate) ? ( + + Duration:{" "} + {dayjs.duration(dayjs(endDate).diff(startDate)).asSeconds()}s + + ) : undefined} + + } + showArrow + > + + + + ) : undefined; + +export default DagRunInfo; diff --git a/airflow/ui/src/components/Time.tsx b/airflow/ui/src/components/Time.tsx index d7fb97e94ca3..82af311c8fa7 100644 --- a/airflow/ui/src/components/Time.tsx +++ b/airflow/ui/src/components/Time.tsx @@ -34,9 +34,14 @@ dayjs.extend(advancedFormat); type Props = { readonly datetime?: string | null; readonly format?: string; + readonly showTooltip?: boolean; }; -const Time = ({ datetime, format = defaultFormat }: Props) => { +const Time = ({ + datetime, + format = defaultFormat, + showTooltip = true, +}: Props) => { const { selectedTimezone } = useTimezone(); const time = dayjs(datetime); @@ -51,7 +56,11 @@ const Time = ({ datetime, format = defaultFormat }: Props) => { diff --git a/airflow/ui/src/pages/DagsList/Dag/Header.tsx b/airflow/ui/src/pages/DagsList/Dag/Header.tsx index 81c48eb28bcf..b17ea1ff0181 100644 --- a/airflow/ui/src/pages/DagsList/Dag/Header.tsx +++ b/airflow/ui/src/pages/DagsList/Dag/Header.tsx @@ -30,12 +30,11 @@ import { FiCalendar, FiPlay } from "react-icons/fi"; import type { DAGResponse, DAGRunResponse } from "openapi/requests/types.gen"; import { DagIcon } from "src/assets/DagIcon"; -import Time from "src/components/Time"; +import DagRunInfo from "src/components/DagRunInfo"; import { TogglePause } from "src/components/TogglePause"; import { Tooltip } from "src/components/ui"; import { DagTags } from "../DagTags"; -import { LatestRun } from "../LatestRun"; export const Header = ({ dag, @@ -81,17 +80,26 @@ export const Header = ({ Last Run - - + {Boolean(latestRun) && latestRun !== undefined ? ( + + ) : undefined} Next Run - {Boolean(dag?.next_dagrun) ? ( - - + {Boolean(dag?.next_dagrun) && dag !== undefined ? ( + ) : undefined}
diff --git a/airflow/ui/src/pages/DagsList/DagCard.tsx b/airflow/ui/src/pages/DagsList/DagCard.tsx index 3c16fb35f8ae..8cd21bd69a31 100644 --- a/airflow/ui/src/pages/DagsList/DagCard.tsx +++ b/airflow/ui/src/pages/DagsList/DagCard.tsx @@ -28,12 +28,11 @@ import { import { Link as RouterLink } from "react-router-dom"; import type { DAGWithLatestDagRunsResponse } from "openapi/requests/types.gen"; -import Time from "src/components/Time"; +import DagRunInfo from "src/components/DagRunInfo"; import { TogglePause } from "src/components/TogglePause"; import { Tooltip } from "src/components/ui"; import { DagTags } from "./DagTags"; -import { LatestRun } from "./LatestRun"; import { RecentRuns } from "./RecentRuns"; import { Schedule } from "./Schedule"; @@ -41,58 +40,76 @@ type Props = { readonly dag: DAGWithLatestDagRunsResponse; }; -export const DagCard = ({ dag }: Props) => ( - - { + const [latestRun] = dag.latest_dag_runs; + + return ( + - - - - - {dag.dag_display_name} - - - - - - - - - - - - - Schedule - - - - - - Latest Run - - - - - - Next Run - - - - - -); + + + + + + {dag.dag_display_name} + + + + + + + + + + + + + Schedule + + + + + + Latest Run + + {latestRun ? ( + + ) : undefined} + + + + Next Run + + {Boolean(dag.next_dagrun) ? ( + + ) : undefined} + + + + + ); +}; diff --git a/airflow/ui/src/pages/DagsList/DagsList.tsx b/airflow/ui/src/pages/DagsList/DagsList.tsx index 1c3fe5b3c0ae..c787b6874dd6 100644 --- a/airflow/ui/src/pages/DagsList/DagsList.tsx +++ b/airflow/ui/src/pages/DagsList/DagsList.tsx @@ -34,13 +34,13 @@ import type { DagRunState, DAGWithLatestDagRunsResponse, } from "openapi/requests/types.gen"; +import DagRunInfo from "src/components/DagRunInfo"; import { DataTable } from "src/components/DataTable"; import { ToggleTableDisplay } from "src/components/DataTable/ToggleTableDisplay"; import type { CardDef } from "src/components/DataTable/types"; import { useTableURLState } from "src/components/DataTable/useTableUrlState"; import { ErrorAlert } from "src/components/ErrorAlert"; import { SearchBar } from "src/components/SearchBar"; -import Time from "src/components/Time"; import { TogglePause } from "src/components/TogglePause"; import { Select } from "src/components/ui"; import { @@ -53,7 +53,6 @@ import { pluralize } from "src/utils"; import { DagCard } from "./DagCard"; import { DagTags } from "./DagTags"; import { DagsFilters } from "./DagsFilters"; -import { LatestRun } from "./LatestRun"; import { Schedule } from "./Schedule"; const columns: Array> = [ @@ -89,16 +88,27 @@ const columns: Array> = [ accessorKey: "next_dagrun", cell: ({ row: { original } }) => Boolean(original.next_dagrun) ? ( -