Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ type Props = {
| Record<string, string>
| string
| {
actual: string | { _dark: string; _light: string };
planned: string | { _dark: string; _light: string };
primary: string | { _dark: string; _light: string };
secondary: string | { _dark: string; _light: string };
};
readonly cellData: CalendarCellData | undefined;
readonly index?: number;
Expand Down Expand Up @@ -64,7 +64,7 @@ export const CalendarCell = ({
: [];

const isMixedState =
typeof backgroundColor === "object" && "planned" in backgroundColor && "actual" in backgroundColor;
typeof backgroundColor === "object" && "secondary" in backgroundColor && "primary" in backgroundColor;

const cellBox = isMixedState ? (
<Box
Expand All @@ -82,14 +82,14 @@ export const CalendarCell = ({
width="14px"
>
<Box
bg={backgroundColor.planned}
bg={backgroundColor.secondary}
clipPath="polygon(0 100%, 100% 100%, 0 0)"
height="100%"
position="absolute"
width="100%"
/>
<Box
bg={backgroundColor.actual}
bg={backgroundColor.primary}
clipPath="polygon(100% 0, 100% 100%, 0 0)"
height="100%"
position="absolute"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,48 @@ type Props = {
readonly viewMode: CalendarColorMode;
};

type LegendColorType =
| Record<string, string>
| string
| { primary: Record<string, string> | string; secondary: Record<string, string> | string };

const LegendIcon = ({ color, cursor }: { readonly color: LegendColorType; readonly cursor?: string }) => {
const isMixedState = typeof color === "object" && "primary" in color && "secondary" in color;

if (isMixedState) {
return (
<Box
borderRadius="2px"
boxShadow="sm"
cursor={cursor}
height="14px"
overflow="hidden"
position="relative"
width="14px"
>
<Box
bg={color.secondary}
clipPath="polygon(0 100%, 100% 100%, 0 0)"
height="100%"
position="absolute"
width="100%"
/>
<Box
bg={color.primary}
clipPath="polygon(100% 0, 100% 100%, 0 0)"
height="100%"
position="absolute"
width="100%"
/>
</Box>
);
}

return <Box bg={color} borderRadius="2px" boxShadow="sm" cursor={cursor} height="14px" width="14px" />;
};

export const CalendarLegend = ({ scale, vertical = false, viewMode }: Props) => {
const { t: translate } = useTranslation("dag");
const { t: translate } = useTranslation(["dag", "common"]);

const legendTitle =
viewMode === "failed" ? translate("overview.buttons.failedRun_other") : translate("calendar.totalRuns");
Expand All @@ -54,7 +94,9 @@ export const CalendarLegend = ({ scale, vertical = false, viewMode }: Props) =>
<VStack gap={0.5}>
{[...scale.legendItems].reverse().map(({ color, label }) => (
<Tooltip content={`${label} ${viewMode === "failed" ? "failed" : "runs"}`} key={label}>
<Box bg={color} borderRadius="2px" cursor="pointer" height="14px" width="14px" />
<Box>
<LegendIcon color={color} cursor="pointer" />
</Box>
</Tooltip>
))}
</VStack>
Expand All @@ -70,7 +112,9 @@ export const CalendarLegend = ({ scale, vertical = false, viewMode }: Props) =>
<HStack gap={0.5}>
{scale.legendItems.map(({ color, label }) => (
<Tooltip content={`${label} ${viewMode === "failed" ? "failed" : "runs"}`} key={label}>
<Box bg={color} borderRadius="2px" cursor="pointer" height="14px" width="14px" />
<Box>
<LegendIcon color={color} cursor="pointer" />
</Box>
</Tooltip>
))}
</HStack>
Expand All @@ -83,42 +127,49 @@ export const CalendarLegend = ({ scale, vertical = false, viewMode }: Props) =>

<Box>
<HStack gap={4} justify="center" wrap="wrap">
{viewMode === "total" && (
<>
<HStack gap={2}>
<LegendIcon color={{ _dark: "green.700", _light: "green.400" }} />
<Text color="fg.muted" fontSize="xs">
{translate("common:states.success")}
</Text>
</HStack>
<HStack gap={2}>
<LegendIcon color={{ _dark: "cyan.700", _light: "cyan.400" }} />
<Text color="fg.muted" fontSize="xs">
{translate("common:states.running")}
</Text>
</HStack>
</>
)}

<HStack gap={2}>
<LegendIcon color={{ _dark: "red.700", _light: "red.400" }} />
<Text color="fg.muted" fontSize="xs">
{translate("common:states.failed")}
</Text>
</HStack>

<HStack gap={2}>
<Box bg={PLANNED_COLOR} borderRadius="2px" boxShadow="sm" height="14px" width="14px" />
<Text color="fg.muted" fontSize="xs">
{translate("common:states.planned")}
</Text>
</HStack>

<HStack gap={2}>
<Box
borderRadius="2px"
boxShadow="sm"
height="14px"
overflow="hidden"
position="relative"
width="14px"
>
<Box
bg={PLANNED_COLOR}
clipPath="polygon(0 100%, 100% 100%, 0 0)"
height="100%"
position="absolute"
width="100%"
/>
<Box
bg={
<LegendIcon
color={{
primary:
viewMode === "failed"
? { _dark: "red.700", _light: "red.400" }
: { _dark: "green.700", _light: "green.400" }
}
clipPath="polygon(100% 0, 100% 100%, 0 0)"
height="100%"
position="absolute"
width="100%"
/>
</Box>
: { _dark: "green.700", _light: "green.400" },
secondary: PLANNED_COLOR,
}}
/>
<Text color="fg.muted" fontSize="xs">
{translate("calendar.legend.mixed")}
{translate("dag:calendar.legend.mixed")}
</Text>
</HStack>
</HStack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type Props = {
const stateColorMap = {
failed: "failed.solid",
planned: "stone.solid",
queued: "queued.solid",
running: "running.solid",
success: "success.solid",
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const EMPTY_COLOR = { _dark: "gray.700", _light: "gray.100" };
const PLANNED_COLOR = { _dark: "stone.600", _light: "stone.500" };
const DEFAULT_TOTAL_COLOR = { _dark: "green.700", _light: "green.400" };
const DEFAULT_FAILED_COLOR = { _dark: "red.700", _light: "red.400" };
const DEFAULT_RUNNING_COLOR = { _dark: "cyan.700", _light: "cyan.400" };

const EMPTY_COUNTS: RunCounts = {
failed: 0,
Expand Down Expand Up @@ -158,8 +159,8 @@ describe("createCalendarScale", () => {
});

expect(scale.getColor({ ...EMPTY_COUNTS, planned: 1, success: 1, total: 2 })).toEqual({
actual: DEFAULT_TOTAL_COLOR,
planned: PLANNED_COLOR,
primary: DEFAULT_TOTAL_COLOR,
secondary: PLANNED_COLOR,
});
});

Expand All @@ -171,8 +172,57 @@ describe("createCalendarScale", () => {
});

expect(scale.getColor({ ...EMPTY_COUNTS, queued: 1, success: 1, total: 2 })).toEqual({
actual: DEFAULT_TOTAL_COLOR,
planned: PLANNED_COLOR,
primary: DEFAULT_TOTAL_COLOR,
secondary: PLANNED_COLOR,
});
});
Comment thread
YeonShin marked this conversation as resolved.

it("returns the failed color for a failed-only cell in total mode", () => {
Comment thread
YeonShin marked this conversation as resolved.
const scale = createCalendarScale([run("failed", 1)], {
granularity: "hourly",
timezone: "UTC",
viewMode: "total",
});

expect(scale.getColor({ ...EMPTY_COUNTS, failed: 1, total: 1 })).toEqual(DEFAULT_FAILED_COLOR);
});

it("returns a mixed red and green color for failed and success runs in total mode", () => {
const scale = createCalendarScale([run("failed", 1), run("success", 1)], {
granularity: "hourly",
timezone: "UTC",
viewMode: "total",
});

expect(scale.getColor({ ...EMPTY_COUNTS, failed: 1, success: 1, total: 2 })).toEqual({
primary: DEFAULT_FAILED_COLOR,
secondary: DEFAULT_TOTAL_COLOR,
});
});

it("returns a mixed cyan and green color for running and success runs in total mode", () => {
const scale = createCalendarScale([run("running", 1), run("success", 1)], {
granularity: "hourly",
timezone: "UTC",
viewMode: "total",
});

expect(scale.getColor({ ...EMPTY_COUNTS, running: 1, success: 1, total: 2 })).toEqual({
primary: DEFAULT_RUNNING_COLOR,
secondary: DEFAULT_TOTAL_COLOR,
});
});

it("returns a mixed cyan and red color for running and failed runs in total mode", () => {
const scale = createCalendarScale([run("running", 1), run("failed", 1)], {
granularity: "hourly",
timezone: "UTC",
viewMode: "total",
});

expect(scale.getColor({ ...EMPTY_COUNTS, failed: 1, running: 1, total: 2 })).toEqual({
primary: DEFAULT_FAILED_COLOR,
secondary: DEFAULT_RUNNING_COLOR,
});
});

Expand All @@ -195,8 +245,8 @@ describe("createCalendarScale", () => {
});

expect(scale.getColor({ ...EMPTY_COUNTS, failed: 1, planned: 1, total: 2 })).toEqual({
actual: DEFAULT_FAILED_COLOR,
planned: PLANNED_COLOR,
primary: DEFAULT_FAILED_COLOR,
secondary: PLANNED_COLOR,
});
});

Expand All @@ -218,8 +268,76 @@ describe("createCalendarScale", () => {
});

expect(scale.getColor({ ...EMPTY_COUNTS, failed: 1, queued: 1, total: 2 })).toEqual({
actual: DEFAULT_FAILED_COLOR,
planned: PLANNED_COLOR,
primary: DEFAULT_FAILED_COLOR,
secondary: PLANNED_COLOR,
});
});

it("returns the correct gradient color when runs span across different dates", () => {
const scale = createCalendarScale(
[run("failed", 1, "2026-04-08T10:00:00Z"), run("failed", 5, "2026-04-09T10:00:00Z")],
{
granularity: "hourly",
timezone: "UTC",
viewMode: "total",
},
);

const lowIntensityColor = { _dark: "red.900", _light: "red.200" };
const highIntensityColor = { _dark: "red.300", _light: "red.800" };

expect(scale.getColor({ ...EMPTY_COUNTS, failed: 1, total: 1 })).toEqual(lowIntensityColor);
expect(scale.getColor({ ...EMPTY_COUNTS, failed: 5, total: 5 })).toEqual(highIntensityColor);
});

it("prioritizes failed over running over success when multiple actual states coexist with pending", () => {
const scale = createCalendarScale([run("planned", 1), run("failed", 1), run("success", 1)], {
granularity: "hourly",
timezone: "UTC",
viewMode: "total",
});

expect(scale.getColor({ ...EMPTY_COUNTS, failed: 1, planned: 1, success: 1, total: 3 })).toEqual({
primary: DEFAULT_FAILED_COLOR,
secondary: PLANNED_COLOR,
});
});

it("returns an empty scale when no data is provided", () => {
const scale = createCalendarScale([], {
granularity: "hourly",
timezone: "UTC",
viewMode: "total",
});

expect(scale.type).toBe("empty");
expect(scale.getColor(EMPTY_COUNTS)).toEqual(EMPTY_COLOR);
expect(scale.legendItems).toEqual([{ color: EMPTY_COLOR, label: "0" }]);
});

it("prioritizes running and failed colors when failed, running, and success coexist without pending states", () => {
const scale = createCalendarScale([run("failed", 1), run("running", 1), run("success", 1)], {
granularity: "hourly",
timezone: "UTC",
viewMode: "total",
});

expect(scale.getColor({ ...EMPTY_COUNTS, failed: 1, running: 1, success: 1, total: 3 })).toEqual({
primary: DEFAULT_FAILED_COLOR,
secondary: DEFAULT_RUNNING_COLOR,
});
});

it("returns the correct gradient color for failed mode when failed runs span across different dates", () => {
const scale = createCalendarScale(
[run("failed", 1, "2026-04-08T10:00:00Z"), run("failed", 10, "2026-04-09T10:00:00Z")],
{ granularity: "hourly", timezone: "UTC", viewMode: "failed" },
);

const lowIntensityFailedColor = { _dark: "red.900", _light: "red.200" };
const highIntensityFailedColor = { _dark: "red.300", _light: "red.800" };

expect(scale.getColor({ ...EMPTY_COUNTS, failed: 1, total: 1 })).toEqual(lowIntensityFailedColor);
expect(scale.getColor({ ...EMPTY_COUNTS, failed: 10, total: 10 })).toEqual(highIntensityFailedColor);
});
});
Loading
Loading