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) ? (
-
+
) : undefined,
enableSorting: false,
header: "Next Dag Run",
},
{
accessorKey: "latest_dag_runs",
- cell: ({ row: { original } }) => (
-
- ),
+ cell: ({ row: { original } }) =>
+ original.latest_dag_runs[0] ? (
+
+ ) : undefined,
enableSorting: false,
header: "Last Dag Run",
},