You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by "RoyLee1224 (via GitHub)" <gi...@apache.org> on 2025/08/08 00:00:08 UTC
[PR] [WIP] Add Calendar view for Dag [airflow]
RoyLee1224 opened a new pull request, #54252:
URL: https://github.com/apache/airflow/pull/54252
## Related issues, discussions and PRs
#54138
#52008
#50907
#52748
## Key Features
- New "Calendar" Tab is added to the Dag page
- Dual Granularity View: Daily & Hourly View
- Tooltips: Hovering over a cell displays a tooltip with the specific date/time and the total number of runs.
- Dynamic Legend: A color-coded legend at the bottom explains the run density.
## Screenshots
https://github.com/user-attachments/assets/586a78de-d333-44bc-83d6-ee99a8fa09f5
### Hourly View
<img width="431" height="420" alt="hourly" src="https://github.com/user-attachments/assets/1f5dc0ea-fcb0-46ae-b780-bef3df97d846" />
### Daily View
<img width="1510" height="409" alt="daily" src="https://github.com/user-attachments/assets/d6851ff1-a0d2-49c5-8812-3706ac48c9fd" />
## To-Do
- [ ] Enhance Cell Coloring by State: Update the `getColor` logic to reflect the final run states (e.g., success -> green, failed -> red, mixed -> yellow, planned -> blue).
- [ ] Improve Tooltip Performance: Implement debounce for the tooltip similar to `GridTI.tsx` to prevent performance issues on rapid mouse movements.
- [ ] Refine Dark Mode UI: Adjust with ColorPalette.
- [ ] Improve UI Responsiveness: Ensure the calendar grid displays correctly on various screen sizes.
<!--
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.
-->
<!--
Thank you for contributing! Please make sure that your code changes
are covered with tests. And in case of new features or big changes
remember to adjust the documentation.
Feel free to ping committers for the review!
In case of an existing issue, reference it using one of the following:
closes: #ISSUE
related: #ISSUE
How to write a good git commit message:
http://chris.beams.io/posts/git-commit/
-->
<!-- Please keep an empty line above the dashes. -->
---
**^ Add meaningful description above**
Read the **[Pull Request Guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#pull-request-guidelines)** for more information.
In case of fundamental code changes, an Airflow Improvement Proposal ([AIP](https://cwiki.apache.org/confluence/display/AIRFLOW/Airflow+Improvement+Proposals)) is needed.
In case of a new dependency, check compliance with the [ASF 3rd Party License Policy](https://www.apache.org/legal/resolved.html#category-x).
In case of backwards incompatible changes please leave a note in a newsfragment file, named `{pr_number}.significant.rst` or `{issue_number}.significant.rst`, in [airflow-core/newsfragments](https://github.com/apache/airflow/tree/main/airflow-core/newsfragments).
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "RoyLee1224 (via GitHub)" <gi...@apache.org>.
RoyLee1224 commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3193397977
Hi all, thanks for such a productive discussion.
As I'm not a heavy Airflow user myself, I've been trying to imagine the kinds of problems this new Calendar View could help solve from a user's perspective.
My thinking is that if we can align on the key questions users will ask, we can better shape the final design. Here are a few situations that come to mind:
## Use Cases
### 1. A quick "Health Check" - "How has my Dag been running lately?"
Just a quick glance to see if the past month/week was mostly stable (green) or if there are emerging issues (red/yellow).
### 2. Spotting problems after a change - "I deployed an update to this Dag. Did it cause any new problems?"
Compare the Dag's "before vs. after" for version changes.
### 3. Understanding the severity of a problem - "I see some failures. Which day needs my immediate attention?"
It seems important to help users distinguish between a minor issue (e.g., 1 failure out of 20 runs) and a critical outage (e.g., 5 failures out of 5 runs). Both are problems, but the second one is clearly more severe.
## Proposed Solution: Start Clean, Provide Detail on Demand
### Default View:
Keep it simple and clean, focusing on the primary state (Success, Failed, Mixed). With optional "Show Total Runs" button.
### Tooltips for Severity Assessment:
When a user hovers over a day/hour, the tooltip can display a mini bar chart or stacked bar chart showing the success vs. failure counts. I think this is more intuitive than a pie chart, especially when comparing days with different total runs.
### Filter Bar:
This would allow users to ask very specific questions to find exactly what they're looking for. For example:
- Filter by Success Rate (e.g., > 50% or < 20%).
- Filter by Total Runs (e.g., > 10 or < 5).
This is just my current line of thinking, and I'm very curious to hear from the maintainers and other experienced users. Do these scenarios make sense? Are there other important situations I might be missing?
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "bbovenzi (via GitHub)" <gi...@apache.org>.
bbovenzi merged PR #54252:
URL: https://github.com/apache/airflow/pull/54252
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "bbovenzi (via GitHub)" <gi...@apache.org>.
bbovenzi commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2286425750
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/Calendar.tsx:
##########
@@ -0,0 +1,280 @@
+/*!
+ * 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, Text, IconButton, Button, ButtonGroup } from "@chakra-ui/react";
+import { keyframes } from "@emotion/react";
+import dayjs from "dayjs";
+import { useState, useMemo } from "react";
+import { useTranslation } from "react-i18next";
+import { FiMinus, FiPlus, FiChevronLeft, FiChevronRight } from "react-icons/fi";
+import { useParams } from "react-router-dom";
+import { useLocalStorage } from "usehooks-ts";
+
+import { useCalendarServiceGetCalendar } from "openapi/queries";
+import { ErrorAlert } from "src/components/ErrorAlert";
+
+import { CalendarLegend } from "./CalendarLegend";
+import { DailyCalendarView } from "./DailyCalendarView";
+import { HourlyCalendarView } from "./HourlyCalendarView";
+
+const spin = keyframes`
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+`;
+
+export const Calendar = () => {
+ const { dagId = "" } = useParams();
+ const { t: translate } = useTranslation("dag");
+ const [cellSize, setCellSize] = useLocalStorage("calendar-cell-size", 18);
+ const [selectedDate, setSelectedDate] = useState(dayjs());
+ const [granularity, setGranularity] = useLocalStorage<"daily" | "hourly">("calendar-granularity", "daily");
Review Comment:
Let's default to hourly. I think its more useful most of the time.
##########
airflow-core/src/airflow/ui/src/router.tsx:
##########
@@ -161,6 +162,7 @@ export const routerConfig = [
{ element: <Overview />, index: true },
{ element: <DagRuns />, path: "runs" },
{ element: <Tasks />, path: "tasks" },
+ { element: <Calendar />, path: "calendar" },
Review Comment:
I fear we might have too many tabs. What if we move this into Overview for now since that page is quite empty?
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "jscheffl (via GitHub)" <gi...@apache.org>.
jscheffl commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3193558306
> Here's a demo video of filter & show total
Very cool!
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "RoyLee1224 (via GitHub)" <gi...@apache.org>.
RoyLee1224 commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2280494308
##########
airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json:
##########
@@ -5,6 +5,26 @@
"reason": "Reason",
"title": "Dependencies Blocking Task From Getting Scheduled"
},
+ "calendar": {
+ "cellSize": "Cell Size",
+ "daily": "Daily",
+ "decreaseSize": "Decrease size",
+ "hourly": "Hourly",
+ "increaseSize": "Increase size",
+ "legend": {
+ "successRateSpectrum": "Success Rate Spectrum",
+ "tooltips": {
+ "failed": "Less than 20% Success",
+ "success100": "100% Success",
+ "successRate20": "20%+ Success Rate",
+ "successRate40": "40%+ Success Rate",
+ "successRate60": "60%+ Success Rate",
+ "successRate80": "80%+ Success Rate"
Review Comment:
Sure, let me fix it.
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "jscheffl (via GitHub)" <gi...@apache.org>.
jscheffl commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3215796085
> With 14px cell size, the 10px font (2xs) is a bit tight. Here are the options:
For me both is OK
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "RoyLee1224 (via GitHub)" <gi...@apache.org>.
RoyLee1224 commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3215827173
> Third, I think we can always get away with less white space between each cell too which should help with the size of the calendars
Here’s a quick comparison between the current `gap=1` and `gap=0.5`:
| gap 1 | gap 0.5 |
| :---: | :---: |
| <img src="https://github.com/user-attachments/assets/28d986b6-a255-437b-b1e0-bff44e34f63f" width="550"> | <img src="https://github.com/user-attachments/assets/3c47af8e-b283-480b-8497-c99553a8a4a2" width="550">
Please let me know what you think!
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "RoyLee1224 (via GitHub)" <gi...@apache.org>.
RoyLee1224 commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2265430701
##########
airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/dag.json:
##########
@@ -5,6 +5,31 @@
"reason": "原因",
"title": "依賴 (Dependencies) 阻礙任務排程"
},
+ "calendar": {
+ "cellSize": "格子大小",
+ "daily": "每日",
+ "decreaseSize": "縮小尺寸",
+ "error": "載入日曆資料時發生錯誤",
+ "hourly": "每小時",
+ "increaseSize": "增大尺寸",
+ "legend": {
+ "successRateSpectrum": "成功率光譜",
+ "tooltips": {
+ "failed": "成功率低於20%",
+ "success100": "100%成功",
+ "successRate20": "20%以上成功率",
+ "successRate40": "40%以上成功率",
+ "successRate60": "60%以上成功率",
+ "successRate80": "80%以上成功率"
+ }
+ },
+ "less": "較少",
+ "loading": "載入中...",
+ "more": "較多",
+ "noData": "沒有可用的資料",
+ "px": "像素",
+ "week": "第 {{weekNumber}} 週"
Review Comment:
Oops, forgot to modify the en-translations, I'll fix it.
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "RoyLee1224 (via GitHub)" <gi...@apache.org>.
RoyLee1224 commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3173021186
## Update
https://github.com/user-attachments/assets/3e060b0b-542a-471a-803c-440cc269ebbf
Hi all, I've pushed the following updates based on the review:
### Layout:
- Made legend responsive and vertical in hourly view.
- Abbreviated weekday names to a single letter(Mo -> M) for smaller cell sizes (< 18px).
### Theming:
- Replaced all hardcoded colors with theme tokens to enable dark mode support.
- Adjusted `success.400` and `success.500` in the theme for better readability. I've confirmed this change won't affect other parts of the UI.
The theme change:
| Before | After |
| :---: | :---: |
| <img src="https://github.com/user-attachments/assets/f80ee3be-5dd9-416b-982d-a795bea45a7f" width="350"> | <img src="https://github.com/user-attachments/assets/d44e7307-f0b6-400e-ba0f-ce2dab45d729" width="350"> |
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "bbovenzi (via GitHub)" <gi...@apache.org>.
bbovenzi commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2294705246
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/useDelayedTooltip.ts:
##########
@@ -0,0 +1,60 @@
+/*!
+ * 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 { useRef } from "react";
+
+export const useDelayedTooltip = (delayMs: number = 200) => {
Review Comment:
Why do we need this if we're using the chakra tooltip? It already has `openDelay` and `closeDelay` props
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "bbovenzi (via GitHub)" <gi...@apache.org>.
bbovenzi commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3202589328
We probably want to add better bar charts to the overview page too so that this calendar view is not trying to do too much.
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "bbovenzi (via GitHub)" <gi...@apache.org>.
bbovenzi commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2294713175
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/DailyCalendarView.tsx:
##########
@@ -0,0 +1,140 @@
+/*!
+ * 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.
+ */
+
+/*
+ * 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, Text } from "@chakra-ui/react";
+import dayjs from "dayjs";
+import { useTranslation } from "react-i18next";
+
+import type { CalendarTimeRangeResponse } from "openapi/requests/types.gen";
+
+import { CalendarTooltip } from "./CalendarTooltip";
+import { createTooltipContent, generateDailyCalendarData, getCalendarCellColor } from "./calendarUtils";
+import type { CalendarColorMode } from "./types";
+import { useDelayedTooltip } from "./useDelayedTooltip";
+
+type Props = {
+ readonly colorMode: CalendarColorMode;
+ readonly data: Array<CalendarTimeRangeResponse>;
+ readonly selectedYear: number;
+};
+
+export const DailyCalendarView = ({ colorMode, data, selectedYear }: Props) => {
+ const { t: translate } = useTranslation("dag");
+ const dailyData = generateDailyCalendarData(data, selectedYear);
+ const { handleMouseEnter, handleMouseLeave } = useDelayedTooltip();
+
+ const weekdays = [
+ translate("calendar.weekdays.sunday"),
+ translate("calendar.weekdays.monday"),
+ translate("calendar.weekdays.tuesday"),
+ translate("calendar.weekdays.wednesday"),
+ translate("calendar.weekdays.thursday"),
+ translate("calendar.weekdays.friday"),
+ translate("calendar.weekdays.saturday"),
+ ];
+
+ return (
+ <Box mb={4}>
+ <Box display="flex" mb={2}>
+ <Box width="50px" />
+ <Box display="flex" gap={1}>
+ {dailyData.map((week, index) => (
+ <Box key={`month-${week[0]?.date ?? index}`} position="relative" width="18px">
+ {Boolean(week[0] && dayjs(week[0].date).date() <= 7) && (
+ <Text color="fg.muted" fontSize="xs" left="0" position="absolute" top="-20px">
+ {dayjs(week[0]?.date).format("MMM")}
+ </Text>
+ )}
+ </Box>
+ ))}
+ </Box>
+ </Box>
+ <Box display="flex" gap={2}>
+ <Box display="flex" flexDirection="column" gap={1}>
+ {weekdays.map((day) => (
+ <Box
+ alignItems="center"
+ color="fg.muted"
+ display="flex"
+ fontSize="xs"
+ height="18px"
+ justifyContent="flex-end"
+ key={day}
+ pr={2}
+ width="40px"
+ >
+ {day}
+ </Box>
+ ))}
+ </Box>
+ <Box display="flex" gap={1}>
+ {dailyData.map((week, weekIndex) => (
+ <Box display="flex" flexDirection="column" gap={1} key={`week-${week[0]?.date ?? weekIndex}`}>
+ {week.map((day) => {
+ const dayDate = dayjs(day.date);
+ const isInSelectedYear = dayDate.year() === selectedYear;
+
+ if (!isInSelectedYear) {
+ return <Box bg="transparent" height="18px" key={day.date} width="18px" />;
+ }
+
+ return (
+ <Box
+ key={day.date}
+ onMouseEnter={handleMouseEnter}
+ onMouseLeave={handleMouseLeave}
+ position="relative"
+ >
+ <Box
+ _hover={{ transform: "scale(1.1)" }}
+ bg={getCalendarCellColor(day.runs, colorMode)}
+ borderRadius="2px"
+ cursor="pointer"
+ height="18px"
+ width="18px"
Review Comment:
Third, I think we can always get away with less white space between each cell too which should help with the size of the calendars
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "bbovenzi (via GitHub)" <gi...@apache.org>.
bbovenzi commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2294690351
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/CalendarLegend.tsx:
##########
@@ -0,0 +1,113 @@
+/*!
+ * 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, Text, VStack } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
+
+import { Tooltip } from "src/components/ui";
+
+import type { CalendarColorMode } from "./types";
+
+type Props = {
+ readonly colorMode: CalendarColorMode;
+ readonly vertical?: boolean;
+};
+
+const totalRunsLegendData = [
+ { color: { _dark: "gray.700", _light: "gray.100" }, label: "0" },
+ { color: { _dark: "green.300", _light: "green.200" }, label: "1-5" },
+ { color: { _dark: "green.500", _light: "green.400" }, label: "6-15" },
+ { color: { _dark: "green.700", _light: "green.600" }, label: "16-25" },
+ { color: { _dark: "green.900", _light: "green.800" }, label: "26+" },
+];
+
+const failedRunsLegendData = [
+ { color: { _dark: "gray.700", _light: "gray.100" }, label: "0" },
+ { color: { _dark: "red.300", _light: "red.200" }, label: "1-2" },
+ { color: { _dark: "red.500", _light: "red.400" }, label: "3-5" },
+ { color: { _dark: "red.700", _light: "red.600" }, label: "6-10" },
+ { color: { _dark: "red.900", _light: "red.800" }, label: "11+" },
+];
Review Comment:
Can be in another PR, but I think the range should be scaled relative to the max count in the timeframe
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "potiuk (via GitHub)" <gi...@apache.org>.
potiuk commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3181237821
I don't think mixing "grid" calendar view and pie-charts is a good idea, it's extremely mentally difficult to graph. I think what would work better are mini bar-charts (likely horizontal) showing the different counts.
Then you could also communicate the "type" of count as the position, not only colour.
Something like that (but consistent type sequence/colors repeated in every day grid.
<img width="512" height="512" alt="image" src="https://github.com/user-attachments/assets/d6b66ccb-8a1b-42fb-b795-c66985833df1" />
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "RoyLee1224 (via GitHub)" <gi...@apache.org>.
RoyLee1224 commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2280499967
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/DailyCalendarView.tsx:
##########
@@ -0,0 +1,129 @@
+/*!
+ * 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.
+ */
+
+/*
+ * 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, Text } from "@chakra-ui/react";
+import dayjs from "dayjs";
+
+import type { CalendarTimeRangeResponse } from "openapi/requests/types.gen";
+
+import { CalendarTooltip } from "./CalendarTooltip";
+import { createTooltipContent, generateDailyCalendarData, getCalendarCellColor } from "./calendarUtils";
+import { useDelayedTooltip } from "./useDelayedTooltip";
+
+type Props = {
+ readonly cellSize: number;
+ readonly data: Array<CalendarTimeRangeResponse>;
+ readonly selectedYear: number;
+};
+
+export const DailyCalendarView = ({ cellSize, data, selectedYear }: Props) => {
+ const dailyData = generateDailyCalendarData(data, selectedYear);
+ const { handleMouseEnter, handleMouseLeave } = useDelayedTooltip();
+
+ return (
+ <Box mb={4}>
+ <Box display="flex" mb={2}>
+ <Box width="50px" />
+ <Box display="flex" gap={1}>
+ {dailyData.map((week, index) => (
+ <Box key={`month-${week[0]?.date ?? index}`} position="relative" width={`${cellSize}px`}>
+ {Boolean(week[0] && dayjs(week[0].date).date() <= 7) && (
+ <Text color="fg.muted" fontSize="xs" left="0" position="absolute" top="-20px">
+ {dayjs(week[0]?.date).format("MMM")}
+ </Text>
+ )}
+ </Box>
+ ))}
+ </Box>
+ </Box>
+ <Box display="flex" gap={2}>
+ <Box display="flex" flexDirection="column" gap={1}>
+ {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((day) => (
Review Comment:
Sure, I'll complete the full weekday name translations for daily view.
For the hourly view, currently, I'm abbreviating weekdays to a single letter when space is limited (< 18px), using `dayjs().format("dd").charAt(0)`.
Should we translate the months and weekdays, or keep them as English abbreviations?
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "RoyLee1224 (via GitHub)" <gi...@apache.org>.
RoyLee1224 commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3193402903
Here's a demo video of filter & show total
https://github.com/user-attachments/assets/83576ca2-75b1-4b15-bb23-4100bf077eb9
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "RoyLee1224 (via GitHub)" <gi...@apache.org>.
RoyLee1224 commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2265430474
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/Calendar.tsx:
##########
@@ -0,0 +1,355 @@
+/* eslint-disable max-lines */
+
+/*!
+ * 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, Text, IconButton, Button } from "@chakra-ui/react";
+import { keyframes } from "@emotion/react";
+import dayjs from "dayjs";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { FiMinus, FiPlus, FiChevronLeft, FiChevronRight } from "react-icons/fi";
+import { useParams } from "react-router-dom";
+import { useLocalStorage } from "usehooks-ts";
+
+import { useCalendarServiceGetCalendar } from "openapi/queries";
+import { ErrorAlert } from "src/components/ErrorAlert";
+import { Tooltip } from "src/components/ui";
+
+import { DailyCalendarView } from "./DailyCalendarView";
+import { HourlyCalendarView } from "./HourlyCalendarView";
+
+const spin = keyframes`
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+`;
+
+export const Calendar = () => {
+ const { dagId = "" } = useParams();
+ const { t: translate } = useTranslation("dag");
+ const [cellSize, setCellSize] = useLocalStorage("calendar-cell-size", 18);
+ const [selectedYear, setSelectedYear] = useState(dayjs().year());
+ const [selectedMonth, setSelectedMonth] = useState(dayjs().month());
+ const [granularity, setGranularity] = useLocalStorage<"daily" | "hourly">("calendar-granularity", "daily");
+
+ const currentYear = dayjs().year();
+ const currentMonth = dayjs().month();
+
+ const getDateRange = () => {
+ if (granularity === "daily") {
+ return {
+ logicalDateGte: `${selectedYear}-01-01T00:00:00Z`,
+ logicalDateLte: `${selectedYear}-12-31T23:59:59Z`,
+ };
+ } else {
+ const monthStart = dayjs().year(selectedYear).month(selectedMonth).startOf("month");
+ const monthEnd = dayjs().year(selectedYear).month(selectedMonth).endOf("month");
+
+ return {
+ logicalDateGte: monthStart.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ logicalDateLte: monthEnd.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ };
+ }
+ };
+
+ const { data, error, isLoading } = useCalendarServiceGetCalendar(
+ {
+ dagId,
+ granularity,
+ ...getDateRange(),
+ },
+ undefined,
+ { enabled: Boolean(dagId) },
+ );
+
+ const renderLegend = () => (
+ <Box>
+ {/* Success Rate Spectrum */}
+ <Box mb={4}>
+ <Text color="gray.700" fontSize="sm" fontWeight="medium" mb={3} textAlign="center">
+ {translate("calendar.legend.successRateSpectrum")}
+ </Text>
+ <HStack gap={3} justify="center">
+ <Text color="gray.600" fontSize="xs">
+ {translate("common:states.success")}
+ </Text>
+ <HStack borderRadius="full" boxShadow="sm" gap={0} overflow="hidden">
+ <Tooltip content={translate("calendar.legend.tooltips.success100")} openDelay={300}>
+ <Box bg="#008000" cursor="pointer" height="20px" width="32px" />
+ </Tooltip>
+ <Tooltip content={translate("calendar.legend.tooltips.successRate80")} openDelay={300}>
+ <Box bg="#16A34A" cursor="pointer" height="20px" width="24px" />
+ </Tooltip>
+ <Tooltip content={translate("calendar.legend.tooltips.successRate60")} openDelay={300}>
+ <Box bg="#22C55E" cursor="pointer" height="20px" width="24px" />
+ </Tooltip>
+ <Tooltip content={translate("calendar.legend.tooltips.successRate40")} openDelay={300}>
+ <Box bg="#EAB308" cursor="pointer" height="20px" width="24px" />
+ </Tooltip>
+ <Tooltip content={translate("calendar.legend.tooltips.successRate20")} openDelay={300}>
+ <Box bg="#F97316" cursor="pointer" height="20px" width="24px" />
+ </Tooltip>
+ <Tooltip content={translate("calendar.legend.tooltips.failed")} openDelay={300}>
+ <Box bg="#DC2626" cursor="pointer" height="20px" width="32px" />
+ </Tooltip>
+ </HStack>
+ <Text color="gray.600" fontSize="xs">
+ {translate("common:states.failed")}
+ </Text>
+ </HStack>
+ </Box>
+
+ {/* Single State Colors */}
+ <Box>
+ <Text color="gray.700" fontSize="sm" fontWeight="medium" mb={3} textAlign="center">
+ {translate("common:state")}
+ </Text>
+ <HStack gap={4} justify="center" wrap="wrap">
+ <HStack gap={2}>
+ <Box bg="#3182CE" borderRadius="full" boxShadow="sm" height="16px" width="16px" />
+ <Text color="gray.600" fontSize="sm">
+ {translate("common:states.running")}
+ </Text>
+ </HStack>
+ <HStack gap={2}>
+ <Box bg="#F1E7DA" borderRadius="full" boxShadow="sm" height="16px" width="16px" />
+ <Text color="gray.600" fontSize="sm">
+ {translate("common:states.planned")}
+ </Text>
+ </HStack>
+ <HStack gap={2}>
+ <Box bg="#808080" borderRadius="full" boxShadow="sm" height="16px" width="16px" />
+ <Text color="gray.600" fontSize="sm">
+ {translate("common:states.queued")}
+ </Text>
+ </HStack>
+ <HStack gap={2}>
+ <Box bg="#ebedf0" borderRadius="full" boxShadow="sm" height="16px" width="16px" />
+ <Text color="gray.600" fontSize="sm">
+ {translate("common:states.no_status")}
+ </Text>
+ </HStack>
+ </HStack>
+ </Box>
+ </Box>
+ );
+
+ const renderCalendarContent = () => (
+ <Box>
+ {granularity === "daily" ? (
+ <>
+ <DailyCalendarView cellSize={cellSize} data={data?.dag_runs ?? []} selectedYear={selectedYear} />
+ {renderLegend()}
Review Comment:
My mistake. I was testing the layout and I forgot to refactor it. I'll change it right away.
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "ashb (via GitHub)" <gi...@apache.org>.
ashb commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3180453749
I think seeing how it works in a couple of different situations would be good:
1. Multiple times a day, some success, some failed
2. Daily
3. Once every other day
4. Once a week.
The calendar view has (that I know of/can think of) two primary uses: 1) what happened over time, and 2) when is the dag _going_ to run. We need to support both really
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "ashb (via GitHub)" <gi...@apache.org>.
ashb commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3194512414
> Here's a demo video of filter & show total
> filter_showtotal.mp4
Nice.
What do the "Daily" and "Hourly" buttons do? How does a dag behave if it runs once a day? What about when it's 4 times a day?
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "RoyLee1224 (via GitHub)" <gi...@apache.org>.
RoyLee1224 commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3177322585
- Remove link styling from current period labels when already selected
- Change week labels to muted color to indicate non-interactive state
- Update daily/hourly toggle buttons to match grid/graph view styling using ButtonGroup
- Modify scheduled state color for dark mode
| Light | Dark |
| :---: | :---: |
| <img src="https://github.com/user-attachments/assets/7230540b-e745-45ab-bdd9-a0ed6203982c" width="350"> | <img src="https://github.com/user-attachments/assets/cbf0f646-e734-4e5a-b6ad-aa63a9c00b55" width="350"> |
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "RoyLee1224 (via GitHub)" <gi...@apache.org>.
RoyLee1224 commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3215036595
@bbovenzi Sure, sounds good! Those filters were just my experiment on a different branch, and I agree that simplifying is the right way to go.
I've updated the PR with the changes you suggested:
- remove cellsize changing function
- GitHub-style heatmap with buttons toggle between total count and failure count
- default to hourly
Please let me know what you think!
https://github.com/user-attachments/assets/0defe10a-6e43-4aab-9595-7de8103f1eca
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "RoyLee1224 (via GitHub)" <gi...@apache.org>.
RoyLee1224 commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2294739645
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/DailyCalendarView.tsx:
##########
@@ -0,0 +1,140 @@
+/*!
+ * 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.
+ */
+
+/*
+ * 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, Text } from "@chakra-ui/react";
+import dayjs from "dayjs";
+import { useTranslation } from "react-i18next";
+
+import type { CalendarTimeRangeResponse } from "openapi/requests/types.gen";
+
+import { CalendarTooltip } from "./CalendarTooltip";
+import { createTooltipContent, generateDailyCalendarData, getCalendarCellColor } from "./calendarUtils";
+import type { CalendarColorMode } from "./types";
+import { useDelayedTooltip } from "./useDelayedTooltip";
+
+type Props = {
+ readonly colorMode: CalendarColorMode;
+ readonly data: Array<CalendarTimeRangeResponse>;
+ readonly selectedYear: number;
+};
+
+export const DailyCalendarView = ({ colorMode, data, selectedYear }: Props) => {
+ const { t: translate } = useTranslation("dag");
+ const dailyData = generateDailyCalendarData(data, selectedYear);
+ const { handleMouseEnter, handleMouseLeave } = useDelayedTooltip();
+
+ const weekdays = [
+ translate("calendar.weekdays.sunday"),
+ translate("calendar.weekdays.monday"),
+ translate("calendar.weekdays.tuesday"),
+ translate("calendar.weekdays.wednesday"),
+ translate("calendar.weekdays.thursday"),
+ translate("calendar.weekdays.friday"),
+ translate("calendar.weekdays.saturday"),
+ ];
+
+ return (
+ <Box mb={4}>
+ <Box display="flex" mb={2}>
+ <Box width="50px" />
+ <Box display="flex" gap={1}>
+ {dailyData.map((week, index) => (
+ <Box key={`month-${week[0]?.date ?? index}`} position="relative" width="18px">
+ {Boolean(week[0] && dayjs(week[0].date).date() <= 7) && (
+ <Text color="fg.muted" fontSize="xs" left="0" position="absolute" top="-20px">
+ {dayjs(week[0]?.date).format("MMM")}
+ </Text>
+ )}
+ </Box>
+ ))}
+ </Box>
+ </Box>
+ <Box display="flex" gap={2}>
+ <Box display="flex" flexDirection="column" gap={1}>
+ {weekdays.map((day) => (
+ <Box
+ alignItems="center"
+ color="fg.muted"
+ display="flex"
+ fontSize="xs"
+ height="18px"
+ justifyContent="flex-end"
+ key={day}
+ pr={2}
+ width="40px"
+ >
+ {day}
+ </Box>
+ ))}
+ </Box>
+ <Box display="flex" gap={1}>
+ {dailyData.map((week, weekIndex) => (
+ <Box display="flex" flexDirection="column" gap={1} key={`week-${week[0]?.date ?? weekIndex}`}>
+ {week.map((day) => {
+ const dayDate = dayjs(day.date);
+ const isInSelectedYear = dayDate.year() === selectedYear;
+
+ if (!isInSelectedYear) {
+ return <Box bg="transparent" height="18px" key={day.date} width="18px" />;
+ }
+
+ return (
+ <Box
+ key={day.date}
+ onMouseEnter={handleMouseEnter}
+ onMouseLeave={handleMouseLeave}
+ position="relative"
+ >
+ <Box
+ _hover={{ transform: "scale(1.1)" }}
+ bg={getCalendarCellColor(day.runs, colorMode)}
+ borderRadius="2px"
+ cursor="pointer"
+ height="18px"
+ width="18px"
Review Comment:
Sure, I'm on it.
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "bbovenzi (via GitHub)" <gi...@apache.org>.
bbovenzi commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3220817950
Oh and #protm
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "ashb (via GitHub)" <gi...@apache.org>.
ashb commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3180458799
One option for color-blind safe palletes https://colorbrewer2.org/#type=diverging&scheme=BrBG&n=3 (though it does nothing for the "we need to introduce new colors")
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "jscheffl (via GitHub)" <gi...@apache.org>.
jscheffl commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2270722428
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/DailyCalendarView.tsx:
##########
@@ -0,0 +1,129 @@
+/*!
+ * 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.
+ */
+
+/*
+ * 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, Text } from "@chakra-ui/react";
+import dayjs from "dayjs";
+
+import type { CalendarTimeRangeResponse } from "openapi/requests/types.gen";
+
+import { CalendarTooltip } from "./CalendarTooltip";
+import { createTooltipContent, generateDailyCalendarData, getCalendarCellColor } from "./calendarUtils";
+import { useDelayedTooltip } from "./useDelayedTooltip";
+
+type Props = {
+ readonly cellSize: number;
+ readonly data: Array<CalendarTimeRangeResponse>;
+ readonly selectedYear: number;
+};
+
+export const DailyCalendarView = ({ cellSize, data, selectedYear }: Props) => {
+ const dailyData = generateDailyCalendarData(data, selectedYear);
+ const { handleMouseEnter, handleMouseLeave } = useDelayedTooltip();
+
+ return (
+ <Box mb={4}>
+ <Box display="flex" mb={2}>
+ <Box width="50px" />
+ <Box display="flex" gap={1}>
+ {dailyData.map((week, index) => (
+ <Box key={`month-${week[0]?.date ?? index}`} position="relative" width={`${cellSize}px`}>
+ {Boolean(week[0] && dayjs(week[0].date).date() <= 7) && (
+ <Text color="fg.muted" fontSize="xs" left="0" position="absolute" top="-20px">
+ {dayjs(week[0]?.date).format("MMM")}
+ </Text>
+ )}
+ </Box>
+ ))}
+ </Box>
+ </Box>
+ <Box display="flex" gap={2}>
+ <Box display="flex" flexDirection="column" gap={1}>
+ {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((day) => (
Review Comment:
Oh, yes! overlooked this!
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "ashb (via GitHub)" <gi...@apache.org>.
ashb commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2288636253
##########
airflow-core/src/airflow/ui/src/router.tsx:
##########
@@ -161,6 +162,7 @@ export const routerConfig = [
{ element: <Overview />, index: true },
{ element: <DagRuns />, path: "runs" },
{ element: <Tasks />, path: "tasks" },
+ { element: <Calendar />, path: "calendar" },
Review Comment:
Only downside to this is that fetching the future events can be a relatively expensive calculation (I think? We should check that), so getting it every time you clicked on a DAG might not be the best idea for performance :(
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "jason810496 (via GitHub)" <gi...@apache.org>.
jason810496 commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3221215533
Great work! #protm as well
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "bbovenzi (via GitHub)" <gi...@apache.org>.
bbovenzi commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2294700844
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/Calendar.tsx:
##########
@@ -0,0 +1,270 @@
+/*!
+ * 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, Text, IconButton, Button, ButtonGroup } from "@chakra-ui/react";
+import { keyframes } from "@emotion/react";
+import dayjs from "dayjs";
+import { useState, useMemo } from "react";
+import { useTranslation } from "react-i18next";
+import { FiChevronLeft, FiChevronRight } from "react-icons/fi";
+import { useParams } from "react-router-dom";
+import { useLocalStorage } from "usehooks-ts";
+
+import { useCalendarServiceGetCalendar } from "openapi/queries";
+import { ErrorAlert } from "src/components/ErrorAlert";
+
+import { CalendarLegend } from "./CalendarLegend";
+import { DailyCalendarView } from "./DailyCalendarView";
+import { HourlyCalendarView } from "./HourlyCalendarView";
+
+const spin = keyframes`
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+`;
+
+export const Calendar = () => {
+ const { dagId = "" } = useParams();
+ const { t: translate } = useTranslation("dag");
+ const [selectedDate, setSelectedDate] = useState(dayjs());
+ const [granularity, setGranularity] = useLocalStorage<"daily" | "hourly">("calendar-granularity", "hourly");
+ const [viewMode, setViewMode] = useLocalStorage<"failed" | "total">("calendar-view-mode", "total");
+
+ const currentDate = dayjs();
+
+ const dateRange = useMemo(() => {
+ if (granularity === "daily") {
+ const yearStart = selectedDate.startOf("year");
+ const yearEnd = selectedDate.endOf("year");
+
+ return {
+ logicalDateGte: yearStart.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ logicalDateLte: yearEnd.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ };
+ } else {
+ const monthStart = selectedDate.startOf("month");
+ const monthEnd = selectedDate.endOf("month");
+
+ return {
+ logicalDateGte: monthStart.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ logicalDateLte: monthEnd.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ };
+ }
+ }, [granularity, selectedDate]);
+
+ const { data, error, isLoading } = useCalendarServiceGetCalendar(
+ {
+ dagId,
+ granularity,
+ ...dateRange,
+ },
+ undefined,
+ { enabled: Boolean(dagId) },
+ );
+
+ if (!data && !isLoading) {
+ return (
+ <Box p={4}>
+ <Text>{translate("calendar.noData")}</Text>
+ </Box>
+ );
+ }
+
+ return (
+ <Box p={6}>
+ <ErrorAlert error={error} />
+ <HStack justify="space-between" mb={6}>
+ <HStack gap={4}>
Review Comment:
I think this HStack also needs a margin bottom
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "bbovenzi (via GitHub)" <gi...@apache.org>.
bbovenzi commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2294710745
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/DailyCalendarView.tsx:
##########
@@ -0,0 +1,140 @@
+/*!
+ * 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.
+ */
+
+/*
+ * 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, Text } from "@chakra-ui/react";
+import dayjs from "dayjs";
+import { useTranslation } from "react-i18next";
+
+import type { CalendarTimeRangeResponse } from "openapi/requests/types.gen";
+
+import { CalendarTooltip } from "./CalendarTooltip";
+import { createTooltipContent, generateDailyCalendarData, getCalendarCellColor } from "./calendarUtils";
+import type { CalendarColorMode } from "./types";
+import { useDelayedTooltip } from "./useDelayedTooltip";
+
+type Props = {
+ readonly colorMode: CalendarColorMode;
+ readonly data: Array<CalendarTimeRangeResponse>;
+ readonly selectedYear: number;
+};
+
+export const DailyCalendarView = ({ colorMode, data, selectedYear }: Props) => {
+ const { t: translate } = useTranslation("dag");
+ const dailyData = generateDailyCalendarData(data, selectedYear);
+ const { handleMouseEnter, handleMouseLeave } = useDelayedTooltip();
+
+ const weekdays = [
+ translate("calendar.weekdays.sunday"),
+ translate("calendar.weekdays.monday"),
+ translate("calendar.weekdays.tuesday"),
+ translate("calendar.weekdays.wednesday"),
+ translate("calendar.weekdays.thursday"),
+ translate("calendar.weekdays.friday"),
+ translate("calendar.weekdays.saturday"),
+ ];
+
+ return (
+ <Box mb={4}>
+ <Box display="flex" mb={2}>
+ <Box width="50px" />
+ <Box display="flex" gap={1}>
+ {dailyData.map((week, index) => (
+ <Box key={`month-${week[0]?.date ?? index}`} position="relative" width="18px">
+ {Boolean(week[0] && dayjs(week[0].date).date() <= 7) && (
+ <Text color="fg.muted" fontSize="xs" left="0" position="absolute" top="-20px">
+ {dayjs(week[0]?.date).format("MMM")}
+ </Text>
+ )}
+ </Box>
+ ))}
+ </Box>
+ </Box>
+ <Box display="flex" gap={2}>
+ <Box display="flex" flexDirection="column" gap={1}>
+ {weekdays.map((day) => (
+ <Box
+ alignItems="center"
+ color="fg.muted"
+ display="flex"
+ fontSize="xs"
+ height="18px"
+ justifyContent="flex-end"
+ key={day}
+ pr={2}
+ width="40px"
+ >
+ {day}
+ </Box>
+ ))}
+ </Box>
+ <Box display="flex" gap={1}>
+ {dailyData.map((week, weekIndex) => (
+ <Box display="flex" flexDirection="column" gap={1} key={`week-${week[0]?.date ?? weekIndex}`}>
+ {week.map((day) => {
+ const dayDate = dayjs(day.date);
+ const isInSelectedYear = dayDate.year() === selectedYear;
+
+ if (!isInSelectedYear) {
+ return <Box bg="transparent" height="18px" key={day.date} width="18px" />;
+ }
+
+ return (
+ <Box
+ key={day.date}
+ onMouseEnter={handleMouseEnter}
+ onMouseLeave={handleMouseLeave}
+ position="relative"
+ >
+ <Box
+ _hover={{ transform: "scale(1.1)" }}
+ bg={getCalendarCellColor(day.runs, colorMode)}
+ borderRadius="2px"
+ cursor="pointer"
+ height="18px"
+ width="18px"
Review Comment:
First, we specify this cells a ton. I wonder if we can consolidate it to a component to reuse.
Second, I agree with Jens. Its very large. What if we set it to 14x14 to match the grid view?
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "ashb (via GitHub)" <gi...@apache.org>.
ashb commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3194511588
@RoyLee1224 You've missed one crucial use case: Seeing when the Dag _will_ run. Not so useful with a daily or hourly scheduler, but crucial for more complex scheduling patterns, and was the original reason Calendar view was added to Airflow 2's UI
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "RoyLee1224 (via GitHub)" <gi...@apache.org>.
RoyLee1224 commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3215792928
With 14px cell size, the 10px font (2xs) is a bit tight. Here are the options:
- Reduce the font size to 9px.
- Use single-letter abbreviations for the days (M, T, W) while keeping the 10px font.
For now, I've implemented **Option B** in the latest commit, but I'm happy to change it depending on your preference. Let me know what you think!
Option | Preview
-- | --
Before | <img src="https://github.com/user-attachments/assets/cd8c25f7-4f27-4750-aae0-ca44927e585f" width="550" />
Option A: Reduce Font Size | <img src="https://github.com/user-attachments/assets/b5c5c22e-c281-495f-bd45-2f9d854176e8" width="550" />
Option B: Single-Letter Days | <img src="https://github.com/user-attachments/assets/4cb6251e-03ad-4f42-90ee-9948712a7198" width="550" />
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "bbovenzi (via GitHub)" <gi...@apache.org>.
bbovenzi commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2294705246
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/useDelayedTooltip.ts:
##########
@@ -0,0 +1,60 @@
+/*!
+ * 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 { useRef } from "react";
+
+export const useDelayedTooltip = (delayMs: number = 200) => {
Review Comment:
Why do we need this if we're using the chakra tooltip? It already has `openDelay` and `closeDelay` props
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "RoyLee1224 (via GitHub)" <gi...@apache.org>.
RoyLee1224 commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2265430701
##########
airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/dag.json:
##########
@@ -5,6 +5,31 @@
"reason": "原因",
"title": "依賴 (Dependencies) 阻礙任務排程"
},
+ "calendar": {
+ "cellSize": "格子大小",
+ "daily": "每日",
+ "decreaseSize": "縮小尺寸",
+ "error": "載入日曆資料時發生錯誤",
+ "hourly": "每小時",
+ "increaseSize": "增大尺寸",
+ "legend": {
+ "successRateSpectrum": "成功率光譜",
+ "tooltips": {
+ "failed": "成功率低於20%",
+ "success100": "100%成功",
+ "successRate20": "20%以上成功率",
+ "successRate40": "40%以上成功率",
+ "successRate60": "60%以上成功率",
+ "successRate80": "80%以上成功率"
+ }
+ },
+ "less": "較少",
+ "loading": "載入中...",
+ "more": "較多",
+ "noData": "沒有可用的資料",
+ "px": "像素",
+ "week": "第 {{weekNumber}} 週"
Review Comment:
Sure, I forgot to modify the en-translations as well, I'll fix it.
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "jscheffl (via GitHub)" <gi...@apache.org>.
jscheffl commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2264862119
##########
airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/dag.json:
##########
@@ -5,6 +5,31 @@
"reason": "原因",
"title": "依賴 (Dependencies) 阻礙任務排程"
},
+ "calendar": {
+ "cellSize": "格子大小",
+ "daily": "每日",
+ "decreaseSize": "縮小尺寸",
+ "error": "載入日曆資料時發生錯誤",
+ "hourly": "每小時",
+ "increaseSize": "增大尺寸",
+ "legend": {
+ "successRateSpectrum": "成功率光譜",
+ "tooltips": {
+ "failed": "成功率低於20%",
+ "success100": "100%成功",
+ "successRate20": "20%以上成功率",
+ "successRate40": "40%以上成功率",
+ "successRate60": "60%以上成功率",
+ "successRate80": "80%以上成功率"
+ }
+ },
+ "less": "較少",
+ "loading": "載入中...",
+ "more": "較多",
+ "noData": "沒有可用的資料",
+ "px": "像素",
+ "week": "第 {{weekNumber}} 週"
Review Comment:
The translation seems to be inconsistent to EN - can you fix this up?
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/Calendar.tsx:
##########
@@ -0,0 +1,355 @@
+/* eslint-disable max-lines */
+
+/*!
+ * 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, Text, IconButton, Button } from "@chakra-ui/react";
+import { keyframes } from "@emotion/react";
+import dayjs from "dayjs";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { FiMinus, FiPlus, FiChevronLeft, FiChevronRight } from "react-icons/fi";
+import { useParams } from "react-router-dom";
+import { useLocalStorage } from "usehooks-ts";
+
+import { useCalendarServiceGetCalendar } from "openapi/queries";
+import { ErrorAlert } from "src/components/ErrorAlert";
+import { Tooltip } from "src/components/ui";
+
+import { DailyCalendarView } from "./DailyCalendarView";
+import { HourlyCalendarView } from "./HourlyCalendarView";
+
+const spin = keyframes`
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+`;
+
+export const Calendar = () => {
+ const { dagId = "" } = useParams();
+ const { t: translate } = useTranslation("dag");
+ const [cellSize, setCellSize] = useLocalStorage("calendar-cell-size", 18);
+ const [selectedYear, setSelectedYear] = useState(dayjs().year());
+ const [selectedMonth, setSelectedMonth] = useState(dayjs().month());
+ const [granularity, setGranularity] = useLocalStorage<"daily" | "hourly">("calendar-granularity", "daily");
+
+ const currentYear = dayjs().year();
+ const currentMonth = dayjs().month();
+
+ const getDateRange = () => {
+ if (granularity === "daily") {
+ return {
+ logicalDateGte: `${selectedYear}-01-01T00:00:00Z`,
+ logicalDateLte: `${selectedYear}-12-31T23:59:59Z`,
+ };
+ } else {
+ const monthStart = dayjs().year(selectedYear).month(selectedMonth).startOf("month");
+ const monthEnd = dayjs().year(selectedYear).month(selectedMonth).endOf("month");
+
+ return {
+ logicalDateGte: monthStart.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ logicalDateLte: monthEnd.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ };
+ }
+ };
+
+ const { data, error, isLoading } = useCalendarServiceGetCalendar(
+ {
+ dagId,
+ granularity,
+ ...getDateRange(),
+ },
+ undefined,
+ { enabled: Boolean(dagId) },
+ );
+
+ const renderLegend = () => (
+ <Box>
+ {/* Success Rate Spectrum */}
+ <Box mb={4}>
+ <Text color="gray.700" fontSize="sm" fontWeight="medium" mb={3} textAlign="center">
+ {translate("calendar.legend.successRateSpectrum")}
+ </Text>
+ <HStack gap={3} justify="center">
+ <Text color="gray.600" fontSize="xs">
+ {translate("common:states.success")}
+ </Text>
+ <HStack borderRadius="full" boxShadow="sm" gap={0} overflow="hidden">
+ <Tooltip content={translate("calendar.legend.tooltips.success100")} openDelay={300}>
+ <Box bg="#008000" cursor="pointer" height="20px" width="32px" />
+ </Tooltip>
+ <Tooltip content={translate("calendar.legend.tooltips.successRate80")} openDelay={300}>
+ <Box bg="#16A34A" cursor="pointer" height="20px" width="24px" />
+ </Tooltip>
+ <Tooltip content={translate("calendar.legend.tooltips.successRate60")} openDelay={300}>
+ <Box bg="#22C55E" cursor="pointer" height="20px" width="24px" />
+ </Tooltip>
+ <Tooltip content={translate("calendar.legend.tooltips.successRate40")} openDelay={300}>
+ <Box bg="#EAB308" cursor="pointer" height="20px" width="24px" />
+ </Tooltip>
+ <Tooltip content={translate("calendar.legend.tooltips.successRate20")} openDelay={300}>
+ <Box bg="#F97316" cursor="pointer" height="20px" width="24px" />
+ </Tooltip>
+ <Tooltip content={translate("calendar.legend.tooltips.failed")} openDelay={300}>
+ <Box bg="#DC2626" cursor="pointer" height="20px" width="32px" />
+ </Tooltip>
+ </HStack>
+ <Text color="gray.600" fontSize="xs">
+ {translate("common:states.failed")}
+ </Text>
+ </HStack>
+ </Box>
+
+ {/* Single State Colors */}
+ <Box>
+ <Text color="gray.700" fontSize="sm" fontWeight="medium" mb={3} textAlign="center">
+ {translate("common:state")}
+ </Text>
+ <HStack gap={4} justify="center" wrap="wrap">
+ <HStack gap={2}>
+ <Box bg="#3182CE" borderRadius="full" boxShadow="sm" height="16px" width="16px" />
+ <Text color="gray.600" fontSize="sm">
+ {translate("common:states.running")}
+ </Text>
+ </HStack>
+ <HStack gap={2}>
+ <Box bg="#F1E7DA" borderRadius="full" boxShadow="sm" height="16px" width="16px" />
+ <Text color="gray.600" fontSize="sm">
+ {translate("common:states.planned")}
+ </Text>
+ </HStack>
+ <HStack gap={2}>
+ <Box bg="#808080" borderRadius="full" boxShadow="sm" height="16px" width="16px" />
+ <Text color="gray.600" fontSize="sm">
+ {translate("common:states.queued")}
+ </Text>
+ </HStack>
+ <HStack gap={2}>
+ <Box bg="#ebedf0" borderRadius="full" boxShadow="sm" height="16px" width="16px" />
+ <Text color="gray.600" fontSize="sm">
+ {translate("common:states.no_status")}
+ </Text>
+ </HStack>
+ </HStack>
+ </Box>
+ </Box>
+ );
+
+ const renderCalendarContent = () => (
+ <Box>
+ {granularity === "daily" ? (
+ <>
+ <DailyCalendarView cellSize={cellSize} data={data?.dag_runs ?? []} selectedYear={selectedYear} />
+ {renderLegend()}
Review Comment:
Why are you adding a fucntion to generate the element tree and not a component to integrate as legend?
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/Calendar.tsx:
##########
@@ -0,0 +1,355 @@
+/* eslint-disable max-lines */
+
+/*!
+ * 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, Text, IconButton, Button } from "@chakra-ui/react";
+import { keyframes } from "@emotion/react";
+import dayjs from "dayjs";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { FiMinus, FiPlus, FiChevronLeft, FiChevronRight } from "react-icons/fi";
+import { useParams } from "react-router-dom";
+import { useLocalStorage } from "usehooks-ts";
+
+import { useCalendarServiceGetCalendar } from "openapi/queries";
+import { ErrorAlert } from "src/components/ErrorAlert";
+import { Tooltip } from "src/components/ui";
+
+import { DailyCalendarView } from "./DailyCalendarView";
+import { HourlyCalendarView } from "./HourlyCalendarView";
+
+const spin = keyframes`
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+`;
+
+export const Calendar = () => {
+ const { dagId = "" } = useParams();
+ const { t: translate } = useTranslation("dag");
+ const [cellSize, setCellSize] = useLocalStorage("calendar-cell-size", 18);
+ const [selectedYear, setSelectedYear] = useState(dayjs().year());
+ const [selectedMonth, setSelectedMonth] = useState(dayjs().month());
+ const [granularity, setGranularity] = useLocalStorage<"daily" | "hourly">("calendar-granularity", "daily");
+
+ const currentYear = dayjs().year();
+ const currentMonth = dayjs().month();
+
+ const getDateRange = () => {
+ if (granularity === "daily") {
+ return {
+ logicalDateGte: `${selectedYear}-01-01T00:00:00Z`,
+ logicalDateLte: `${selectedYear}-12-31T23:59:59Z`,
+ };
+ } else {
+ const monthStart = dayjs().year(selectedYear).month(selectedMonth).startOf("month");
+ const monthEnd = dayjs().year(selectedYear).month(selectedMonth).endOf("month");
+
+ return {
+ logicalDateGte: monthStart.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ logicalDateLte: monthEnd.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ };
+ }
+ };
+
+ const { data, error, isLoading } = useCalendarServiceGetCalendar(
+ {
+ dagId,
+ granularity,
+ ...getDateRange(),
+ },
+ undefined,
+ { enabled: Boolean(dagId) },
+ );
+
+ const renderLegend = () => (
+ <Box>
+ {/* Success Rate Spectrum */}
+ <Box mb={4}>
+ <Text color="gray.700" fontSize="sm" fontWeight="medium" mb={3} textAlign="center">
+ {translate("calendar.legend.successRateSpectrum")}
+ </Text>
+ <HStack gap={3} justify="center">
+ <Text color="gray.600" fontSize="xs">
+ {translate("common:states.success")}
+ </Text>
+ <HStack borderRadius="full" boxShadow="sm" gap={0} overflow="hidden">
+ <Tooltip content={translate("calendar.legend.tooltips.success100")} openDelay={300}>
+ <Box bg="#008000" cursor="pointer" height="20px" width="32px" />
+ </Tooltip>
+ <Tooltip content={translate("calendar.legend.tooltips.successRate80")} openDelay={300}>
+ <Box bg="#16A34A" cursor="pointer" height="20px" width="24px" />
Review Comment:
Colors should use logical colors and not hard coded RGB values.
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/constants.ts:
##########
@@ -0,0 +1,61 @@
+/*!
+ * 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 type { DagRunState } from "./types";
+
+export const CALENDAR_STATE_COLORS = {
+ empty: "#ebedf0",
+ failed: {
+ medium: "#EF4444",
+ pure: "#DC2626",
+ },
+ mixed: {
+ moderate: "#EAB308",
+ poor: "#F97316",
+ },
+ other: "#9CA3AF",
+ planned: {
+ pure: "#F1E7DA",
Review Comment:
As above: Please do not use hard coded RGB.
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "bbovenzi (via GitHub)" <gi...@apache.org>.
bbovenzi commented on PR #54252:
URL: https://github.com/apache/airflow/pull/54252#issuecomment-3180562768
> One option for color-blind safe palletes https://colorbrewer2.org/#type=diverging&scheme=BrBG&n=3 (though it does nothing for the "we need to introduce new colors")
Another question is do we care about the failure rate at all? We could stay consistent with the grid+graph views for task groups, if a single TI out of 100 has failed then show the whole group as failed with a tooltip to give more context.
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Add Calendar view for Dag [airflow]
Posted by "bbovenzi (via GitHub)" <gi...@apache.org>.
bbovenzi commented on code in PR #54252:
URL: https://github.com/apache/airflow/pull/54252#discussion_r2270659143
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/Calendar.tsx:
##########
@@ -0,0 +1,280 @@
+/*!
+ * 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, Text, IconButton, Button, ButtonGroup } from "@chakra-ui/react";
+import { keyframes } from "@emotion/react";
+import dayjs from "dayjs";
+import { useState, useMemo } from "react";
+import { useTranslation } from "react-i18next";
+import { FiMinus, FiPlus, FiChevronLeft, FiChevronRight } from "react-icons/fi";
+import { useParams } from "react-router-dom";
+import { useLocalStorage } from "usehooks-ts";
+
+import { useCalendarServiceGetCalendar } from "openapi/queries";
+import { ErrorAlert } from "src/components/ErrorAlert";
+
+import { CalendarLegend } from "./CalendarLegend";
+import { DailyCalendarView } from "./DailyCalendarView";
+import { HourlyCalendarView } from "./HourlyCalendarView";
+
+const spin = keyframes`
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+`;
+
+export const Calendar = () => {
+ const { dagId = "" } = useParams();
+ const { t: translate } = useTranslation("dag");
+ const [cellSize, setCellSize] = useLocalStorage("calendar-cell-size", 18);
+ const [selectedDate, setSelectedDate] = useState(dayjs());
+ const [granularity, setGranularity] = useLocalStorage<"daily" | "hourly">("calendar-granularity", "daily");
+
+ const currentDate = dayjs();
+
+ const dateRange = useMemo(() => {
+ if (granularity === "daily") {
+ const yearStart = selectedDate.startOf("year");
+ const yearEnd = selectedDate.endOf("year");
+
+ return {
+ logicalDateGte: yearStart.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ logicalDateLte: yearEnd.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ };
+ } else {
+ const monthStart = selectedDate.startOf("month");
+ const monthEnd = selectedDate.endOf("month");
+
+ return {
+ logicalDateGte: monthStart.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ logicalDateLte: monthEnd.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ };
+ }
+ }, [granularity, selectedDate]);
+
+ const { data, error, isLoading } = useCalendarServiceGetCalendar(
+ {
+ dagId,
+ granularity,
+ ...dateRange,
+ },
+ undefined,
+ { enabled: Boolean(dagId) },
+ );
+
+ if (!data && !isLoading) {
+ return (
+ <Box p={4}>
+ <Text>{translate("calendar.noData")}</Text>
+ </Box>
+ );
+ }
+
+ return (
+ <Box p={6}>
+ <ErrorAlert error={error} />
+ <HStack justify="space-between" mb={6}>
+ <HStack gap={4}>
+ {granularity === "daily" ? (
+ <HStack gap={2}>
+ <IconButton
+ aria-label="Previous year"
+ onClick={() => setSelectedDate(selectedDate.subtract(1, "year"))}
+ size="sm"
+ variant="ghost"
+ >
+ <FiChevronLeft />
+ </IconButton>
+ <Text
+ _hover={selectedDate.year() === currentDate.year() ? {} : { textDecoration: "underline" }}
+ color={selectedDate.year() === currentDate.year() ? "fg.info" : "inherit"}
+ cursor={selectedDate.year() === currentDate.year() ? "default" : "pointer"}
+ fontSize="xl"
+ fontWeight="bold"
+ minWidth="120px"
+ onClick={() => {
+ if (selectedDate.year() !== currentDate.year()) {
+ setSelectedDate(currentDate.startOf("year"));
+ }
+ }}
+ textAlign="center"
+ >
+ {selectedDate.year()}
+ </Text>
+ <IconButton
+ aria-label="Next year"
Review Comment:
Let's translate all the aria-labels
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/Calendar.tsx:
##########
@@ -0,0 +1,280 @@
+/*!
+ * 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, Text, IconButton, Button, ButtonGroup } from "@chakra-ui/react";
+import { keyframes } from "@emotion/react";
+import dayjs from "dayjs";
+import { useState, useMemo } from "react";
+import { useTranslation } from "react-i18next";
+import { FiMinus, FiPlus, FiChevronLeft, FiChevronRight } from "react-icons/fi";
+import { useParams } from "react-router-dom";
+import { useLocalStorage } from "usehooks-ts";
+
+import { useCalendarServiceGetCalendar } from "openapi/queries";
+import { ErrorAlert } from "src/components/ErrorAlert";
+
+import { CalendarLegend } from "./CalendarLegend";
+import { DailyCalendarView } from "./DailyCalendarView";
+import { HourlyCalendarView } from "./HourlyCalendarView";
+
+const spin = keyframes`
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+`;
+
+export const Calendar = () => {
+ const { dagId = "" } = useParams();
+ const { t: translate } = useTranslation("dag");
+ const [cellSize, setCellSize] = useLocalStorage("calendar-cell-size", 18);
+ const [selectedDate, setSelectedDate] = useState(dayjs());
+ const [granularity, setGranularity] = useLocalStorage<"daily" | "hourly">("calendar-granularity", "daily");
+
+ const currentDate = dayjs();
+
+ const dateRange = useMemo(() => {
+ if (granularity === "daily") {
+ const yearStart = selectedDate.startOf("year");
+ const yearEnd = selectedDate.endOf("year");
+
+ return {
+ logicalDateGte: yearStart.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ logicalDateLte: yearEnd.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ };
+ } else {
+ const monthStart = selectedDate.startOf("month");
+ const monthEnd = selectedDate.endOf("month");
+
+ return {
+ logicalDateGte: monthStart.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ logicalDateLte: monthEnd.format("YYYY-MM-DD[T]HH:mm:ss[Z]"),
+ };
+ }
+ }, [granularity, selectedDate]);
+
+ const { data, error, isLoading } = useCalendarServiceGetCalendar(
+ {
+ dagId,
+ granularity,
+ ...dateRange,
+ },
+ undefined,
+ { enabled: Boolean(dagId) },
+ );
+
+ if (!data && !isLoading) {
+ return (
+ <Box p={4}>
+ <Text>{translate("calendar.noData")}</Text>
+ </Box>
+ );
+ }
+
+ return (
+ <Box p={6}>
+ <ErrorAlert error={error} />
+ <HStack justify="space-between" mb={6}>
+ <HStack gap={4}>
+ {granularity === "daily" ? (
+ <HStack gap={2}>
+ <IconButton
+ aria-label="Previous year"
Review Comment:
Translation needed
##########
airflow-core/src/airflow/ui/src/pages/Dag/Calendar/DailyCalendarView.tsx:
##########
@@ -0,0 +1,129 @@
+/*!
+ * 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.
+ */
+
+/*
+ * 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, Text } from "@chakra-ui/react";
+import dayjs from "dayjs";
+
+import type { CalendarTimeRangeResponse } from "openapi/requests/types.gen";
+
+import { CalendarTooltip } from "./CalendarTooltip";
+import { createTooltipContent, generateDailyCalendarData, getCalendarCellColor } from "./calendarUtils";
+import { useDelayedTooltip } from "./useDelayedTooltip";
+
+type Props = {
+ readonly cellSize: number;
+ readonly data: Array<CalendarTimeRangeResponse>;
+ readonly selectedYear: number;
+};
+
+export const DailyCalendarView = ({ cellSize, data, selectedYear }: Props) => {
+ const dailyData = generateDailyCalendarData(data, selectedYear);
+ const { handleMouseEnter, handleMouseLeave } = useDelayedTooltip();
+
+ return (
+ <Box mb={4}>
+ <Box display="flex" mb={2}>
+ <Box width="50px" />
+ <Box display="flex" gap={1}>
+ {dailyData.map((week, index) => (
+ <Box key={`month-${week[0]?.date ?? index}`} position="relative" width={`${cellSize}px`}>
+ {Boolean(week[0] && dayjs(week[0].date).date() <= 7) && (
+ <Text color="fg.muted" fontSize="xs" left="0" position="absolute" top="-20px">
+ {dayjs(week[0]?.date).format("MMM")}
+ </Text>
+ )}
+ </Box>
+ ))}
+ </Box>
+ </Box>
+ <Box display="flex" gap={2}>
+ <Box display="flex" flexDirection="column" gap={1}>
+ {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((day) => (
Review Comment:
Should days be translated?
##########
airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json:
##########
@@ -5,6 +5,26 @@
"reason": "Reason",
"title": "Dependencies Blocking Task From Getting Scheduled"
},
+ "calendar": {
+ "cellSize": "Cell Size",
+ "daily": "Daily",
+ "decreaseSize": "Decrease size",
+ "hourly": "Hourly",
+ "increaseSize": "Increase size",
+ "legend": {
+ "successRateSpectrum": "Success Rate Spectrum",
+ "tooltips": {
+ "failed": "Less than 20% Success",
+ "success100": "100% Success",
+ "successRate20": "20%+ Success Rate",
+ "successRate40": "40%+ Success Rate",
+ "successRate60": "60%+ Success Rate",
+ "successRate80": "80%+ Success Rate"
Review Comment:
I wonder if we want to pass the number into the translation so its easier to change the values in the future without new translations
--
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: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org