Skip to content
Draft
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
26 changes: 26 additions & 0 deletions packages/core/src/sidebar/buildSidebarData.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { WorkspaceMode } from "@posthog/shared";
import type { Task, TaskRunStatus } from "@posthog/shared/domain-types";
import { getRepositoryInfo } from "./groupTasks";
import type { TaskData } from "./sidebarData.types";
Expand Down Expand Up @@ -116,6 +117,7 @@ export interface TaskWorkspace {
folderPath?: string | null;
branchName?: string | null;
linkedBranch?: string | null;
mode?: WorkspaceMode;
}

export interface TaskTimestamp {
Expand Down Expand Up @@ -174,6 +176,9 @@ export function deriveTaskData(
folderId: workspace?.folderId || undefined,
taskRunStatus: session?.cloudStatus ?? task.latest_run?.status ?? undefined,
taskRunEnvironment: task.latest_run?.environment ?? undefined,
workspaceMode:
workspace?.mode ??
(task.latest_run?.environment === "cloud" ? "cloud" : undefined),
originProduct,
slackThreadUrl,
folderPath: workspace?.folderPath ?? null,
Expand All @@ -183,6 +188,27 @@ export function deriveTaskData(
};
}

export const ALL_WORKSPACE_MODES: readonly WorkspaceMode[] = [
"worktree",
"local",
"cloud",
];

/**
* Keeps tasks whose workspace mode is in `enabledModes`. Tasks without a known
* mode always pass so an unclassified task never silently disappears.
*/
export function filterByWorkspaceMode(
tasks: TaskData[],
enabledModes: readonly WorkspaceMode[],
): TaskData[] {
if (enabledModes.length >= ALL_WORKSPACE_MODES.length) return tasks;
return tasks.filter(
(task) =>
task.workspaceMode == null || enabledModes.includes(task.workspaceMode),
);
}

function getSortValue(task: TaskData, sortMode: SortMode): number {
return sortMode === "updated" ? task.lastActivityAt : task.createdAt;
}
Expand Down
49 changes: 49 additions & 0 deletions packages/core/src/sidebar/filterByWorkspaceMode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, expect, it } from "vitest";
import { ALL_WORKSPACE_MODES, filterByWorkspaceMode } from "./buildSidebarData";
import type { TaskData } from "./sidebarData.types";

const task = (overrides: Partial<TaskData>): TaskData => ({
id: "t",
title: "t",
createdAt: 0,
lastActivityAt: 0,
isGenerating: false,
isUnread: false,
isPinned: false,
needsPermission: false,
repository: null,
isSuspended: false,
folderPath: null,
cloudPrUrl: null,
branchName: null,
linkedBranch: null,
...overrides,
});

describe("filterByWorkspaceMode", () => {
const worktree = task({ id: "w", workspaceMode: "worktree" });
const local = task({ id: "l", workspaceMode: "local" });
const cloud = task({ id: "c", workspaceMode: "cloud" });
const unknown = task({ id: "u", workspaceMode: undefined });
const tasks = [worktree, local, cloud, unknown];

it("returns all tasks when every mode is enabled", () => {
expect(filterByWorkspaceMode(tasks, ALL_WORKSPACE_MODES)).toEqual(tasks);
});

it("keeps only tasks whose mode is enabled, plus unknown-mode tasks", () => {
expect(filterByWorkspaceMode(tasks, ["local"])).toEqual([local, unknown]);
});

it("keeps multiple enabled modes", () => {
expect(filterByWorkspaceMode(tasks, ["worktree", "cloud"])).toEqual([
worktree,
cloud,
unknown,
]);
});

it("keeps only unknown-mode tasks when nothing is enabled", () => {
expect(filterByWorkspaceMode(tasks, [])).toEqual([unknown]);
});
});
2 changes: 2 additions & 0 deletions packages/core/src/sidebar/sidebarData.types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { WorkspaceMode } from "@posthog/shared";
import type { TaskRunStatus } from "@posthog/shared/domain-types";
import type {
TaskGroup as GenericTaskGroup,
Expand All @@ -18,6 +19,7 @@ export interface TaskData {
folderId?: string;
taskRunStatus?: TaskRunStatus;
taskRunEnvironment?: "local" | "cloud";
workspaceMode?: WorkspaceMode;
originProduct?: string;
slackThreadUrl?: string;
folderPath: string | null;
Expand Down
24 changes: 24 additions & 0 deletions packages/ui/src/features/sidebar/components/TasksHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { useHostTRPCClient } from "@posthog/host-router/react";
import {
Button,
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
MenuLabel,
} from "@posthog/quill";
import type { WorkspaceMode } from "@posthog/shared";
import { useMeQuery } from "@posthog/ui/features/auth/useMeQuery";
import { useFolders } from "@posthog/ui/features/folders/useFolders";
import { useSidebarStore } from "@posthog/ui/features/sidebar/sidebarStore";
Expand All @@ -25,6 +27,12 @@ import { useState } from "react";

const log = logger.scope("tasks-header");

const TASK_TYPE_OPTIONS: { value: WorkspaceMode; label: string }[] = [
{ value: "worktree", label: "Worktree" },
{ value: "local", label: "Local" },
{ value: "cloud", label: "Cloud" },
];

function AddFolderButton() {
const trpcClient = useHostTRPCClient();
const { addFolder } = useFolders();
Expand Down Expand Up @@ -82,6 +90,8 @@ function TaskFilterMenu() {
const setSortMode = useSidebarStore((state) => state.setSortMode);
const setShowAllUsers = useSidebarStore((state) => state.setShowAllUsers);
const setShowInternal = useSidebarStore((state) => state.setShowInternal);
const taskTypeFilter = useSidebarStore((state) => state.taskTypeFilter);
const toggleTaskType = useSidebarStore((state) => state.toggleTaskType);
const { data: currentUser } = useMeQuery();
const isStaff = currentUser?.is_staff === true;

Expand Down Expand Up @@ -126,6 +136,20 @@ function TaskFilterMenu() {
<DropdownMenuRadioItem value="updated">Updated</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>

<DropdownMenuSeparator />

<MenuLabel>Task type</MenuLabel>
{TASK_TYPE_OPTIONS.map((option) => (
<DropdownMenuCheckboxItem
key={option.value}
checked={taskTypeFilter.includes(option.value)}
closeOnClick={false}
onCheckedChange={() => toggleTaskType(option.value)}
>
{option.label}
</DropdownMenuCheckboxItem>
))}

{import.meta.env.DEV && (
<>
<DropdownMenuSeparator />
Expand Down
15 changes: 15 additions & 0 deletions packages/ui/src/features/sidebar/sidebarStore.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ALL_WORKSPACE_MODES } from "@posthog/core/sidebar/buildSidebarData";
import type { WorkspaceMode } from "@posthog/shared";
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { SIDEBAR_MIN_WIDTH } from "./constants";
Expand All @@ -14,6 +16,7 @@ interface SidebarStoreState {
sortMode: "updated" | "created";
showAllUsers: boolean;
showInternal: boolean;
taskTypeFilter: WorkspaceMode[];
}

interface SidebarStoreActions {
Expand All @@ -32,6 +35,7 @@ interface SidebarStoreActions {
setSortMode: (mode: SidebarStoreState["sortMode"]) => void;
setShowAllUsers: (showAllUsers: boolean) => void;
setShowInternal: (showInternal: boolean) => void;
toggleTaskType: (mode: WorkspaceMode) => void;
}

type SidebarStore = SidebarStoreState & SidebarStoreActions;
Expand All @@ -50,6 +54,7 @@ export const useSidebarStore = create<SidebarStore>()(
sortMode: "updated",
showAllUsers: false,
showInternal: false,
taskTypeFilter: [...ALL_WORKSPACE_MODES],
setOpen: (open) => set({ open, hasUserSetOpen: true }),
setOpenAuto: (open) =>
set((state) => (state.hasUserSetOpen ? state : { open })),
Expand Down Expand Up @@ -100,6 +105,12 @@ export const useSidebarStore = create<SidebarStore>()(
setSortMode: (sortMode) => set({ sortMode }),
setShowAllUsers: (showAllUsers) => set({ showAllUsers }),
setShowInternal: (showInternal) => set({ showInternal }),
toggleTaskType: (mode) =>
set((state) => ({
taskTypeFilter: state.taskTypeFilter.includes(mode)
? state.taskTypeFilter.filter((m) => m !== mode)
: [...state.taskTypeFilter, mode],
})),
}),
{
name: "sidebar-storage",
Expand All @@ -114,6 +125,7 @@ export const useSidebarStore = create<SidebarStore>()(
sortMode: state.sortMode,
showAllUsers: state.showAllUsers,
showInternal: state.showInternal,
taskTypeFilter: state.taskTypeFilter,
}),
merge: (persisted, current) => {
const persistedState = persisted as {
Expand All @@ -127,6 +139,7 @@ export const useSidebarStore = create<SidebarStore>()(
sortMode?: SidebarStoreState["sortMode"];
showAllUsers?: boolean;
showInternal?: boolean;
taskTypeFilter?: WorkspaceMode[];
};
return {
...current,
Expand All @@ -145,6 +158,8 @@ export const useSidebarStore = create<SidebarStore>()(
sortMode: persistedState.sortMode ?? current.sortMode,
showAllUsers: persistedState.showAllUsers ?? current.showAllUsers,
showInternal: persistedState.showInternal ?? current.showInternal,
taskTypeFilter:
persistedState.taskTypeFilter ?? current.taskTypeFilter,
};
},
},
Expand Down
11 changes: 9 additions & 2 deletions packages/ui/src/features/sidebar/useSidebarData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
deriveTaskData,
type FullTask,
filterByWorkspaceMode,
filterVisibleTasks,
narrowFullTask,
partitionAndSortTasks,
Expand Down Expand Up @@ -51,6 +52,7 @@ export function useSidebarData({
const organizeMode = useSidebarStore((state) => state.organizeMode);
const sortMode = useSidebarStore((state) => state.sortMode);
const folderOrder = useSidebarStore((state) => state.folderOrder);
const taskTypeFilter = useSidebarStore((state) => state.taskTypeFilter);

const summaryIds = useMemo(
() =>
Expand Down Expand Up @@ -166,9 +168,14 @@ export function useSidebarData({
],
);

const filteredTaskData = useMemo(
() => filterByWorkspaceMode(taskData, taskTypeFilter),
[taskData, taskTypeFilter],
);

const { pinnedTasks, sortedUnpinnedTasks, totalCount } = useMemo(
() => partitionAndSortTasks(taskData, sortMode),
[taskData, sortMode],
() => partitionAndSortTasks(filteredTaskData, sortMode),
[filteredTaskData, sortMode],
);

const { flatTasks, hasMore } = useMemo(
Expand Down
Loading