bbovenzi commented on code in PR #60105:
URL: https://github.com/apache/airflow/pull/60105#discussion_r2665847914


##########
airflow-core/src/airflow/ui/src/pages/DagsList/TaskInstanceSummary.tsx:
##########
@@ -0,0 +1,82 @@
+/*!
+ * 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 { HStack, Spinner, Text } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
+
+import { useTaskInstanceServiceGetTaskInstances } from "openapi/queries";
+import type { TaskInstanceState } from "openapi/requests/types.gen";
+import { StateBadge } from "src/components/StateBadge";
+import { Stat } from "src/components/Stat";
+
+type Props = {
+  readonly dagId: string;
+  readonly runId: string;
+};
+
+export const TaskInstanceSummary = ({ dagId, runId }: Props) => {
+  const { t: translate } = useTranslation("common");
+
+  const { data, isLoading } = useTaskInstanceServiceGetTaskInstances({
+    dagId,
+    dagRunId: runId,
+    limit: 1000,
+  });
+
+  if (isLoading) {
+    return (
+      <Stat label={translate("taskInstanceSummary")}>
+        <Spinner size="sm" />
+      </Stat>
+    );
+  }
+
+  // Count task instances by state
+  const stateCounts: Record<string, number> = {};
+
+  data?.task_instances?.forEach((ti) => {
+    const state = ti.state ?? "no_status";
+
+    stateCounts[state] = (stateCounts[state] ?? 0) + 1;
+  });
+
+  const stateEntries = Object.entries(stateCounts);
+
+  if (stateEntries.length === 0) {
+    return (
+      <Stat label={translate("taskInstanceSummary")}>
+        <Text color="fg.muted" fontSize="sm">
+          —

Review Comment:
   Again, I don't think the empty stat is all that helpful unless its to keep 
the card aligned. If so, we should try to declare <Stat> once instead of four 
times across two files.



##########
airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx:
##########
@@ -104,14 +108,13 @@ export const DagCard = ({ dag }: Props) => {
             </Link>
           ) : undefined}
         </Stat>
-        <Stat data-testid="next-run" label={translate("dagDetails.nextRun")}>
-          {Boolean(dag.next_dagrun_run_after) ? (
-            <DagRunInfo
-              logicalDate={dag.next_dagrun_logical_date}
-              runAfter={dag.next_dagrun_run_after as string}
-            />
-          ) : undefined}
-        </Stat>
+
+        {latestRun ? (
+          <TaskInstanceSummary dagId={dag.dag_id} runId={latestRun.run_id} />
+        ) : (
+          <Stat label={translate("taskInstanceSummary")}>—</Stat>

Review Comment:
   I feel like we shouldn't show anything, not even the label, when there is no 
latest run.



##########
airflow-core/src/airflow/ui/src/pages/DagsList/TaskInstanceSummary.tsx:
##########
@@ -0,0 +1,82 @@
+/*!
+ * 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 { HStack, Spinner, Text } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
+
+import { useTaskInstanceServiceGetTaskInstances } from "openapi/queries";
+import type { TaskInstanceState } from "openapi/requests/types.gen";
+import { StateBadge } from "src/components/StateBadge";
+import { Stat } from "src/components/Stat";
+
+type Props = {
+  readonly dagId: string;
+  readonly runId: string;
+};
+
+export const TaskInstanceSummary = ({ dagId, runId }: Props) => {
+  const { t: translate } = useTranslation("common");
+
+  const { data, isLoading } = useTaskInstanceServiceGetTaskInstances({
+    dagId,
+    dagRunId: runId,
+    limit: 1000,
+  });
+
+  if (isLoading) {
+    return (
+      <Stat label={translate("taskInstanceSummary")}>
+        <Spinner size="sm" />
+      </Stat>
+    );
+  }
+
+  // Count task instances by state
+  const stateCounts: Record<string, number> = {};
+
+  data?.task_instances?.forEach((ti) => {
+    const state = ti.state ?? "no_status";
+
+    stateCounts[state] = (stateCounts[state] ?? 0) + 1;
+  });
+
+  const stateEntries = Object.entries(stateCounts);
+
+  if (stateEntries.length === 0) {
+    return (
+      <Stat label={translate("taskInstanceSummary")}>
+        <Text color="fg.muted" fontSize="sm">
+          —
+        </Text>
+      </Stat>
+    );
+  }
+
+  return (
+    <Stat data-testid="task-instance-summary" 
label={translate("taskInstanceSummary")}>
+      <HStack flexWrap="wrap" gap={1}>
+        {stateEntries.map(([state, count]) => (
+          <StateBadge key={state} state={state as TaskInstanceState}>
+            {count}
+          </StateBadge>
+        ))}
+      </HStack>

Review Comment:
   Can you provide a screenshot using every task state at once? Manually make 
mock data if you need to. But we should make sure that the formatting still 
works.



##########
airflow-core/src/airflow/ui/src/pages/DagsList/TaskInstanceSummary.tsx:
##########
@@ -0,0 +1,82 @@
+/*!
+ * 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 { HStack, Spinner, Text } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
+
+import { useTaskInstanceServiceGetTaskInstances } from "openapi/queries";
+import type { TaskInstanceState } from "openapi/requests/types.gen";
+import { StateBadge } from "src/components/StateBadge";
+import { Stat } from "src/components/Stat";
+
+type Props = {
+  readonly dagId: string;
+  readonly runId: string;
+};
+
+export const TaskInstanceSummary = ({ dagId, runId }: Props) => {
+  const { t: translate } = useTranslation("common");
+
+  const { data, isLoading } = useTaskInstanceServiceGetTaskInstances({
+    dagId,
+    dagRunId: runId,
+    limit: 1000,
+  });

Review Comment:
   This can be a heavy query. Before merging we should test this against a page 
with many large dags and see how it affects the dags list page performance. I 
fear having 50 of these queries going off at once, each fetching 1000 task 
instances, will really slow down the page.



##########
airflow-core/src/airflow/ui/src/pages/DagsList/TaskInstanceSummary.tsx:
##########
@@ -0,0 +1,82 @@
+/*!
+ * 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 { HStack, Spinner, Text } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
+
+import { useTaskInstanceServiceGetTaskInstances } from "openapi/queries";
+import type { TaskInstanceState } from "openapi/requests/types.gen";
+import { StateBadge } from "src/components/StateBadge";
+import { Stat } from "src/components/Stat";
+
+type Props = {
+  readonly dagId: string;
+  readonly runId: string;
+};
+
+export const TaskInstanceSummary = ({ dagId, runId }: Props) => {
+  const { t: translate } = useTranslation("common");
+
+  const { data, isLoading } = useTaskInstanceServiceGetTaskInstances({
+    dagId,
+    dagRunId: runId,
+    limit: 1000,
+  });
+
+  if (isLoading) {
+    return (
+      <Stat label={translate("taskInstanceSummary")}>
+        <Spinner size="sm" />
+      </Stat>
+    );
+  }
+
+  // Count task instances by state
+  const stateCounts: Record<string, number> = {};
+
+  data?.task_instances?.forEach((ti) => {
+    const state = ti.state ?? "no_status";
+
+    stateCounts[state] = (stateCounts[state] ?? 0) + 1;
+  });
+
+  const stateEntries = Object.entries(stateCounts);
+
+  if (stateEntries.length === 0) {
+    return (
+      <Stat label={translate("taskInstanceSummary")}>
+        <Text color="fg.muted" fontSize="sm">
+          —
+        </Text>
+      </Stat>
+    );
+  }
+
+  return (
+    <Stat data-testid="task-instance-summary" 
label={translate("taskInstanceSummary")}>

Review Comment:
   Let's add a test that actually uses this data-testid



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to