Skip to content

Commit 7936c58

Browse files
refactor(mobile): keep presentation logic local
Generated-By: PostHog Code Task-Id: c1bbe3cf-742b-4b24-bf96-d11a18b4cf22
1 parent 6cba9e9 commit 7936c58

9 files changed

Lines changed: 177 additions & 250 deletions

File tree

apps/mobile/src/features/tasks/components/AutomationStatusBadge.tsx

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
import { Text } from "@components/text";
2-
import { getAutomationStatusPresentation } from "@posthog/core/automations/automationStatus";
32
import type { TaskRun } from "@posthog/shared";
43
import { View } from "react-native";
5-
6-
const STATUS_TONE_CLASSES = {
7-
neutral: "bg-gray-4 text-gray-11",
8-
warning: "bg-status-warning/20 text-status-warning",
9-
success: "bg-status-success/20 text-status-success",
10-
error: "bg-status-error/20 text-status-error",
11-
} as const;
4+
import { getAutomationStatusPresentation } from "../utils/automationStatus";
125

136
interface AutomationStatusBadgeProps {
147
enabled: boolean;
@@ -25,9 +18,6 @@ export function AutomationStatusBadge({
2518
lastRunStatus,
2619
lastTaskRunStatus,
2720
});
28-
const runStatusClassName = runStatus
29-
? STATUS_TONE_CLASSES[runStatus.tone]
30-
: null;
3121

3222
return (
3323
<View className="flex-row flex-wrap gap-2">
@@ -42,9 +32,9 @@ export function AutomationStatusBadge({
4232
{enabled ? "Enabled" : "Paused"}
4333
</Text>
4434
</View>
45-
{runStatus && runStatusClassName ? (
46-
<View className={`rounded px-1.5 py-0.5 ${runStatusClassName}`}>
47-
<Text className={`text-xs ${runStatusClassName.split(" ")[1]}`}>
35+
{runStatus ? (
36+
<View className={`rounded px-1.5 py-0.5 ${runStatus.className}`}>
37+
<Text className={`text-xs ${runStatus.className.split(" ")[1]}`}>
4838
{runStatus.label}
4939
</Text>
5040
</View>
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import type { Task } from "@posthog/shared";
2+
import { describe, expect, it } from "vitest";
3+
import { getTaskStatusIconKind } from "./taskStatusIconKind";
4+
5+
function makeTask(latestRun?: Partial<NonNullable<Task["latest_run"]>>): Task {
6+
return {
7+
id: "task-1",
8+
task_number: 1,
9+
slug: "task-1",
10+
title: "Test task",
11+
description: "",
12+
created_at: "2026-01-01T00:00:00Z",
13+
updated_at: "2026-01-01T00:00:00Z",
14+
origin_product: "code",
15+
latest_run: latestRun
16+
? {
17+
id: "run-1",
18+
task: "task-1",
19+
team: 1,
20+
branch: null,
21+
stage: null,
22+
environment: "local",
23+
status: "not_started",
24+
log_url: "",
25+
error_message: null,
26+
output: null,
27+
state: {},
28+
created_at: "2026-01-01T00:00:00Z",
29+
updated_at: "2026-01-01T00:00:00Z",
30+
completed_at: null,
31+
...latestRun,
32+
}
33+
: undefined,
34+
};
35+
}
36+
37+
describe("getTaskStatusIconKind", () => {
38+
it("prioritizes PR over cloud status", () => {
39+
const task = makeTask({
40+
environment: "cloud",
41+
status: "in_progress",
42+
output: { pr_url: "https://github.com/PostHog/code/pull/123" },
43+
});
44+
45+
expect(getTaskStatusIconKind(task)).toBe("pr");
46+
});
47+
48+
it("shows chat for cloud tasks without a PR, regardless of run status", () => {
49+
expect(
50+
getTaskStatusIconKind(
51+
makeTask({ environment: "cloud", status: "queued" }),
52+
),
53+
).toBe("chat");
54+
expect(
55+
getTaskStatusIconKind(
56+
makeTask({ environment: "cloud", status: "in_progress" }),
57+
),
58+
).toBe("chat");
59+
expect(
60+
getTaskStatusIconKind(
61+
makeTask({ environment: "cloud", status: "completed" }),
62+
),
63+
).toBe("chat");
64+
expect(
65+
getTaskStatusIconKind(
66+
makeTask({ environment: "cloud", status: "cancelled" }),
67+
),
68+
).toBe("chat");
69+
});
70+
71+
it("preserves local run-state icons", () => {
72+
expect(
73+
getTaskStatusIconKind(
74+
makeTask({ environment: "local", status: "in_progress" }),
75+
),
76+
).toBe("running");
77+
expect(
78+
getTaskStatusIconKind(
79+
makeTask({ environment: "local", status: "failed" }),
80+
),
81+
).toBe("failed");
82+
});
83+
84+
it("falls back to chat when a task has no run yet", () => {
85+
expect(getTaskStatusIconKind(makeTask())).toBe("chat");
86+
});
87+
});

apps/mobile/src/features/tasks/components/TaskStatusIcon.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { getTaskStatusPresentationKind } from "@posthog/core/tasks/taskStatusPresentation";
21
import type { Task } from "@posthog/shared";
32
import {
43
ChatCircle,
@@ -11,6 +10,7 @@ import {
1110
import { memo, useEffect, useRef } from "react";
1211
import { Animated, Easing } from "react-native";
1312
import { useThemeColors } from "@/lib/theme";
13+
import { getTaskStatusIconKind } from "./taskStatusIconKind";
1414

1515
interface TaskStatusIconProps {
1616
task: Task;
@@ -19,7 +19,7 @@ interface TaskStatusIconProps {
1919

2020
function TaskStatusIconComponent({ task, size = 16 }: TaskStatusIconProps) {
2121
const colors = useThemeColors();
22-
const iconKind = getTaskStatusPresentationKind(task);
22+
const iconKind = getTaskStatusIconKind(task);
2323

2424
const rotation = useRef(new Animated.Value(0)).current;
2525
const isRunning = iconKind === "running";
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { Task } from "@posthog/shared";
2+
3+
export type TaskStatusIconKind =
4+
| "pr"
5+
| "completed"
6+
| "failed"
7+
| "running"
8+
| "started"
9+
| "chat";
10+
11+
export function getTaskStatusIconKind(task: Task): TaskStatusIconKind {
12+
const prUrl = task.latest_run?.output?.pr_url as string | undefined;
13+
const status = task.latest_run?.status;
14+
const environment = task.latest_run?.environment;
15+
16+
if (prUrl) {
17+
return "pr";
18+
}
19+
20+
if (environment === "cloud") {
21+
return "chat";
22+
}
23+
24+
if (status === "completed") {
25+
return "completed";
26+
}
27+
28+
if (status === "failed") {
29+
return "failed";
30+
}
31+
32+
if (status === "in_progress") {
33+
return "running";
34+
}
35+
36+
if (status === "queued") {
37+
return "started";
38+
}
39+
40+
return "chat";
41+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { describe, expect, it } from "vitest";
2+
import { getAutomationStatusPresentation } from "./automationStatus";
3+
4+
describe("automationStatus", () => {
5+
it("shows queued when the linked task run has not started work yet", () => {
6+
expect(
7+
getAutomationStatusPresentation({
8+
lastRunStatus: "running",
9+
lastTaskRunStatus: "queued",
10+
}),
11+
).toMatchObject({ label: "Queued" });
12+
});
13+
14+
it("hides the running badge while the linked task run is actively in progress", () => {
15+
expect(
16+
getAutomationStatusPresentation({
17+
lastRunStatus: "running",
18+
lastTaskRunStatus: "in_progress",
19+
}),
20+
).toBeNull();
21+
});
22+
23+
it("hides the running badge when only the automation-level status is available", () => {
24+
expect(
25+
getAutomationStatusPresentation({ lastRunStatus: "running" }),
26+
).toBeNull();
27+
});
28+
29+
it("falls back to automation status when task-run detail is unavailable", () => {
30+
expect(
31+
getAutomationStatusPresentation({ lastRunStatus: "success" }),
32+
).toMatchObject({ label: "Success" });
33+
});
34+
});
Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,13 @@
1-
export type AutomationTaskRunStatus =
2-
| "not_started"
3-
| "queued"
4-
| "started"
5-
| "in_progress"
6-
| "completed"
7-
| "failed"
8-
| "cancelled";
1+
import type { TaskRun } from "@posthog/shared";
92

103
export interface AutomationStatusInput {
114
lastRunStatus: string | null;
12-
lastTaskRunStatus?: AutomationTaskRunStatus | null;
5+
lastTaskRunStatus?: TaskRun["status"] | null;
136
}
147

15-
export type AutomationStatusTone = "neutral" | "warning" | "success" | "error";
16-
17-
export type AutomationStatusIconKind =
18-
| "queued"
19-
| "success"
20-
| "failed"
21-
| "never-run";
22-
238
export interface AutomationStatusPresentation {
249
label: string;
25-
tone: AutomationStatusTone;
26-
iconKind: AutomationStatusIconKind;
10+
className: string;
2711
}
2812

2913
export function getAutomationStatusPresentation({
@@ -35,24 +19,20 @@ export function getAutomationStatusPresentation({
3519
case "queued":
3620
return {
3721
label: "Queued",
38-
tone: "warning",
39-
iconKind: "queued",
22+
className: "bg-status-warning/20 text-status-warning",
4023
};
41-
case "started":
4224
case "in_progress":
4325
return null;
4426
case "completed":
4527
return {
4628
label: "Success",
47-
tone: "success",
48-
iconKind: "success",
29+
className: "bg-status-success/20 text-status-success",
4930
};
5031
case "failed":
5132
case "cancelled":
5233
return {
5334
label: "Failed",
54-
tone: "error",
55-
iconKind: "failed",
35+
className: "bg-status-error/20 text-status-error",
5636
};
5737
default:
5838
break;
@@ -64,20 +44,17 @@ export function getAutomationStatusPresentation({
6444
case "success":
6545
return {
6646
label: "Success",
67-
tone: "success",
68-
iconKind: "success",
47+
className: "bg-status-success/20 text-status-success",
6948
};
7049
case "failed":
7150
return {
7251
label: "Failed",
73-
tone: "error",
74-
iconKind: "failed",
52+
className: "bg-status-error/20 text-status-error",
7553
};
7654
default:
7755
return {
7856
label: "Never run",
79-
tone: "neutral",
80-
iconKind: "never-run",
57+
className: "bg-gray-4 text-gray-11",
8158
};
8259
}
8360
}

packages/core/src/automations/automationStatus.test.ts

Lines changed: 0 additions & 80 deletions
This file was deleted.

0 commit comments

Comments
 (0)