diff --git a/airflow/ui/src/pages/Dashboard/Dashboard.tsx b/airflow/ui/src/pages/Dashboard/Dashboard.tsx index 0b8d265772227..38727b0809e33 100644 --- a/airflow/ui/src/pages/Dashboard/Dashboard.tsx +++ b/airflow/ui/src/pages/Dashboard/Dashboard.tsx @@ -19,7 +19,7 @@ import { Box, Heading } from "@chakra-ui/react"; import { Health } from "./Health"; -import { Metrics } from "./Metrics"; +import { HistoricalMetrics } from "./HistoricalMetrics"; export const Dashboard = () => ( @@ -28,7 +28,7 @@ export const Dashboard = () => ( - + ); diff --git a/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx new file mode 100644 index 0000000000000..4b9730f544722 --- /dev/null +++ b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/DagRunMetrics.tsx @@ -0,0 +1,52 @@ +/*! + * 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, Heading, HStack } from "@chakra-ui/react"; +import type { DAGRunStates } from "openapi-gen/requests/types.gen"; + +import { MetricSection } from "./MetricSection"; +import { MetricsBadge } from "./MetricsBadge"; + +type DagRunMetricsProps = { + readonly dagRunStates: DAGRunStates; + readonly total: number; +}; + +const DAGRUN_STATES: Array = [ + "queued", + "running", + "success", + "failed", +]; + +export const DagRunMetrics = ({ dagRunStates, total }: DagRunMetricsProps) => ( + + + + Dag Runs + + {DAGRUN_STATES.map((state) => ( + + ))} + +); diff --git a/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx new file mode 100644 index 0000000000000..06ff59b4b09ff --- /dev/null +++ b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx @@ -0,0 +1,127 @@ +/*! + * 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, + HStack, + VStack, + Text, + createListCollection, + type SelectValueChangeDetails, +} from "@chakra-ui/react"; +import dayjs from "dayjs"; +import { useState } from "react"; +import { FiCalendar } from "react-icons/fi"; + +import { useDashboardServiceHistoricalMetrics } from "openapi/queries"; +import { ErrorAlert } from "src/components/ErrorAlert"; +import Time from "src/components/Time"; +import { Select } from "src/components/ui"; + +import { DagRunMetrics } from "./DagRunMetrics"; +import { TaskInstanceMetrics } from "./TaskInstanceMetrics"; + +const timeOptions = createListCollection({ + items: [ + { label: "Last 1 hour", value: "1" }, + { label: "Last 8 hours", value: "8" }, + { label: "Last 12 hours", value: "12" }, + { label: "Last 24 hours", value: "24" }, + ], +}); + +export const HistoricalMetrics = () => { + const defaultHour = "8"; + const now = dayjs(); + const [startDate, setStartDate] = useState( + now.subtract(Number(defaultHour), "hour").toISOString(), + ); + const [endDate, setEndDate] = useState(now.toISOString()); + + const { data, error, isLoading } = useDashboardServiceHistoricalMetrics({ + endDate, + startDate, + }); + + const dagRunTotal = data + ? Object.values(data.dag_run_states).reduce( + (partialSum, value) => partialSum + value, + 0, + ) + : 0; + + const taskRunTotal = data + ? Object.values(data.task_instance_states).reduce( + (partialSum, value) => partialSum + value, + 0, + ) + : 0; + + const handleTimeChange = ({ + value, + }: SelectValueChangeDetails>) => { + const cnow = dayjs(); + + setStartDate(cnow.subtract(Number(value[0]), "hour").toISOString()); + setEndDate(cnow.toISOString()); + }; + + return ( + + + + + + + + + + + {timeOptions.items.map((option) => ( + + {option.label} + + ))} + + + + + + {!isLoading && data !== undefined && ( + + + + + )} + + + ); +}; diff --git a/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx new file mode 100644 index 0000000000000..0dd41b6abc668 --- /dev/null +++ b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricSection.tsx @@ -0,0 +1,73 @@ +/*! + * 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, Flex, HStack, VStack, Text } from "@chakra-ui/react"; + +import { capitalize } from "src/utils"; +import { stateColor } from "src/utils/stateColor"; + +import { MetricsBadge } from "./MetricsBadge"; + +const BAR_WIDTH = 100; +const BAR_HEIGHT = 5; + +type MetricSectionProps = { + readonly runs: number; + readonly state: string; + readonly total: number; +}; + +export const MetricSection = ({ runs, state, total }: MetricSectionProps) => { + // Calculate the given state as a percentage of total and draw a bar + // in state's color with width as state's percentage and remaining width filed as gray + const statePercent = total === 0 ? 0 : ((runs / total) * 100).toFixed(2); + const stateWidth = total === 0 ? 0 : (runs / total) * BAR_WIDTH; + const remainingWidth = BAR_WIDTH - stateWidth; + + return ( + + + + + {capitalize(state)} + + {statePercent}% + + + + + + + ); +}; diff --git a/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricsBadge.tsx b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricsBadge.tsx new file mode 100644 index 0000000000000..26dd3f1767f54 --- /dev/null +++ b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/MetricsBadge.tsx @@ -0,0 +1,30 @@ +/*! + * 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 { Badge } from "@chakra-ui/react"; + +type MetricBadgeProps = { + readonly color: string; + readonly runs: number; +}; + +export const MetricsBadge = ({ color, runs }: MetricBadgeProps) => ( + + {runs} + +); diff --git a/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx new file mode 100644 index 0000000000000..507a0eb3b50c1 --- /dev/null +++ b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/TaskInstanceMetrics.tsx @@ -0,0 +1,57 @@ +/*! + * 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, Heading, HStack } from "@chakra-ui/react"; +import type { TaskInstanceStateCount } from "openapi-gen/requests/types.gen"; + +import { MetricSection } from "./MetricSection"; +import { MetricsBadge } from "./MetricsBadge"; + +type TaskInstanceMetricsProps = { + readonly taskInstanceStates: TaskInstanceStateCount; + readonly total: number; +}; + +const TASK_STATES: Array = [ + "queued", + "running", + "success", + "failed", + "skipped", +]; + +export const TaskInstanceMetrics = ({ + taskInstanceStates, + total, +}: TaskInstanceMetricsProps) => ( + + + + Task Instances + + + {TASK_STATES.map((state) => ( + + ))} + +); diff --git a/airflow/ui/src/pages/Dashboard/HistoricalMetrics/index.tsx b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/index.tsx new file mode 100644 index 0000000000000..e96bf8d5f75bd --- /dev/null +++ b/airflow/ui/src/pages/Dashboard/HistoricalMetrics/index.tsx @@ -0,0 +1,20 @@ +/*! + * 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. + */ + +export { HistoricalMetrics } from "./HistoricalMetrics"; diff --git a/airflow/ui/src/pages/Dashboard/Metrics.tsx b/airflow/ui/src/pages/Dashboard/Metrics.tsx deleted file mode 100644 index d05af7cc285d1..0000000000000 --- a/airflow/ui/src/pages/Dashboard/Metrics.tsx +++ /dev/null @@ -1,244 +0,0 @@ -/*! - * 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, - Flex, - Heading, - HStack, - VStack, - Text, - createListCollection, - type SelectValueChangeDetails, -} from "@chakra-ui/react"; -import dayjs from "dayjs"; -import type { - TaskInstanceStateCount, - DAGRunStates, -} from "openapi-gen/requests/types.gen"; -import { useState } from "react"; -import { FiCalendar } from "react-icons/fi"; - -import { useDashboardServiceHistoricalMetrics } from "openapi/queries"; -import { ErrorAlert } from "src/components/ErrorAlert"; -import Time from "src/components/Time"; -import { Select } from "src/components/ui"; -import { capitalize } from "src/utils"; -import { stateColor } from "src/utils/stateColor"; - -const BAR_WIDTH = 100; -const BAR_HEIGHT = 5; -const DAGRUN_STATES: Array = [ - "queued", - "running", - "success", - "failed", -]; -const TASK_STATES: Array = [ - "queued", - "running", - "success", - "failed", - "skipped", -]; -const timeOptions = createListCollection({ - items: [ - { label: "Last 1 hour", value: "1" }, - { label: "Last 8 hours", value: "8" }, - { label: "Last 12 hours", value: "12" }, - { label: "Last 24 hours", value: "24" }, - ], -}); - -type Props = { - readonly runs: number; - readonly state: string; - readonly total: number; -}; - -type DagRunStateInfoProps = { - readonly dagRunStates: DAGRunStates; - readonly total: number; -}; - -type TaskRunStateInfoProps = { - readonly taskRunStates: TaskInstanceStateCount; - readonly total: number; -}; - -const StateInfo = ({ runs, state, total }: Props) => { - // Calculate the given state as a percentage of total and draw a bar - // in state's color with width as state's percentage and remaining width filed as gray - const statePercent = total === 0 ? 0 : ((runs / total) * 100).toFixed(2); - const stateWidth = total === 0 ? 0 : (runs / total) * BAR_WIDTH; - const remainingWidth = BAR_WIDTH - stateWidth; - - return ( - - - - - {runs} - - {capitalize(state)} - - {statePercent}% - - - - - - - ); -}; - -const DagRunStateInfo = ({ dagRunStates, total }: DagRunStateInfoProps) => - DAGRUN_STATES.map((state) => ( - - )); - -const TaskRunStateInfo = ({ taskRunStates, total }: TaskRunStateInfoProps) => - TASK_STATES.map((state) => ( - - )); - -export const Metrics = () => { - const defaultHour = "8"; - const now = dayjs(); - const [startDate, setStartDate] = useState( - now.subtract(Number(defaultHour), "hour").toISOString(), - ); - const [endDate, setEndDate] = useState(now.toISOString()); - - const { data, error, isLoading } = useDashboardServiceHistoricalMetrics({ - endDate, - startDate, - }); - - const dagRunTotal = data - ? Object.values(data.dag_run_states).reduce( - (partialSum, value) => partialSum + value, - 0, - ) - : 0; - - const taskRunTotal = data - ? Object.values(data.task_instance_states).reduce( - (partialSum, value) => partialSum + value, - 0, - ) - : 0; - - const handleTimeChange = ({ - value, - }: SelectValueChangeDetails>) => { - const cnow = dayjs(); - - setStartDate(cnow.subtract(Number(value[0]), "hour").toISOString()); - setEndDate(cnow.toISOString()); - }; - - return ( - - - - - - - - - - - {timeOptions.items.map((option) => ( - - {option.label} - - ))} - - - - - - {!isLoading && data !== undefined && ( - - - - - {dagRunTotal} - - Dag Runs - - - - - - - {taskRunTotal} - - Task Instances - - - - - )} - - - ); -};