Skip to content
Closed
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
18 changes: 18 additions & 0 deletions packages/core/src/sidebar/groupTasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,20 @@ export function getRepositoryInfo(
return null;
}

export function folderGroupId(folder: {
path: string;
remoteUrl: string | null;
}): string {
if (folder.remoteUrl) {
return normalizeRepoKey(folder.remoteUrl).toLowerCase();
}
return folder.path;
}

export function groupByRepository<T extends GroupableTask>(
tasks: T[],
folderOrder: string[],
allFolders: { path: string; remoteUrl: string | null; name: string }[] = [],
): TaskGroup<T>[] {
const groupMap = new Map<string, TaskGroup<T>>();

Expand All @@ -70,6 +81,13 @@ export function groupByRepository<T extends GroupableTask>(
group.tasks.push(task);
}

for (const folder of allFolders) {
const groupId = folderGroupId(folder);
if (!groupMap.has(groupId)) {
groupMap.set(groupId, { id: groupId, name: folder.name, tasks: [] });
}
}

const groups = Array.from(groupMap.values());

// Disambiguate groups that share a display name (e.g. `posthog/posthog`
Expand Down
37 changes: 37 additions & 0 deletions packages/ui/src/features/sidebar/components/SidebarMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { folderGroupId } from "@posthog/core/sidebar/groupTasks";
import { isTaskActivelyRunning } from "@posthog/core/sidebar/taskRunning";
import { useHostTRPCClient } from "@posthog/host-router/react";
import { Separator } from "@posthog/quill";
Expand All @@ -8,6 +9,8 @@ import {
useArchiveTask,
} from "@posthog/ui/features/archive/useArchiveTask";
import { useCommandCenterStore } from "@posthog/ui/features/command-center/commandCenterStore";
import { useExternalAppAction } from "@posthog/ui/features/external-apps/useExternalAppAction";
import { useFolders } from "@posthog/ui/features/folders/useFolders";
import { useArchivingTasksStore } from "@posthog/ui/features/sidebar/archivingTasksStore";
import { useSidebarStore } from "@posthog/ui/features/sidebar/sidebarStore";
import { useTaskSelectionStore } from "@posthog/ui/features/sidebar/taskSelectionStore";
Expand Down Expand Up @@ -52,6 +55,10 @@ function SidebarMenuComponent() {
const { data: workspaces = {} } = useWorkspaces();
const { markAsViewed } = useTaskViewed();

const { folders, removeFolder } = useFolders();

const openExternalApp = useExternalAppAction();

const { showContextMenu, editingTaskId, setEditingTaskId } =
useTaskContextMenu();
const { archiveTask } = useArchiveTask();
Expand Down Expand Up @@ -216,6 +223,35 @@ function SidebarMenuComponent() {
[hostClient, queryClient, clearSelection, archiveCacheKeys],
);

const handleGroupContextMenu = useCallback(
async (groupId: string, e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const folder = folders.find((f) => folderGroupId(f) === groupId);
if (!folder) return;
try {
const result =
await hostClient.contextMenu.showFolderContextMenu.mutate({
folderName: folder.name,
folderPath: folder.path,
});
if (result.action?.type === "remove") {
await removeFolder(folder.id);
} else if (result.action?.type === "external-app") {
await openExternalApp(
result.action.action,
folder.path,
folder.name,
{ workspace: null },
);
}
} catch (error) {
log.error("Failed to show folder context menu", error);
}
},
[folders, removeFolder, hostClient, openExternalApp],
);

const handleTaskContextMenu = (
taskId: string,
e: React.MouseEvent,
Expand Down Expand Up @@ -431,6 +467,7 @@ function SidebarMenuComponent() {
onTaskTogglePin={handleTaskTogglePin}
onTaskEditSubmit={handleTaskEditSubmit}
onTaskEditCancel={handleTaskEditCancel}
onGroupContextMenu={handleGroupContextMenu}
hasMore={sidebarData.hasMore}
/>
)}
Expand Down
72 changes: 41 additions & 31 deletions packages/ui/src/features/sidebar/components/TaskListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import { PointerSensor } from "@dnd-kit/dom";
import type { DragDropEvents } from "@dnd-kit/react";
import { DragDropProvider } from "@dnd-kit/react";
import { GitBranch } from "@phosphor-icons/react";
import { groupTasksByRelativeDate } from "@posthog/core/sidebar/groupTasks";
import {
folderGroupId,
groupTasksByRelativeDate,
} from "@posthog/core/sidebar/groupTasks";
import { mostRecentRunEnvironment } from "@posthog/core/sidebar/runEnvironment";
import type {
TaskData,
TaskGroup,
} from "@posthog/core/sidebar/sidebarData.types";
import { MenuLabel } from "@posthog/quill";
import { normalizeRepoKey } from "@posthog/shared";
import { builderHog } from "@posthog/ui/assets/hedgehogs";
import { useFolders } from "@posthog/ui/features/folders/useFolders";
import { useArchivingTasksStore } from "@posthog/ui/features/sidebar/archivingTasksStore";
Expand Down Expand Up @@ -47,6 +49,7 @@ interface TaskListViewProps {
newTitle: string,
) => void;
onTaskEditCancel: () => void;
onGroupContextMenu?: (groupId: string, e: React.MouseEvent) => void;
hasMore: boolean;
}

Expand Down Expand Up @@ -143,6 +146,7 @@ export function TaskListView({
onTaskTogglePin,
onTaskEditSubmit,
onTaskEditCancel,
onGroupContextMenu,
hasMore,
}: TaskListViewProps) {
const selectedIdSet = useMemo(
Expand Down Expand Up @@ -273,12 +277,7 @@ export function TaskListView({
<Flex direction="column">
{groupedTasks.map((group, index) => {
const isExpanded = !collapsedSections.has(group.id);
const folder = folders.find(
(f) =>
(f.remoteUrl &&
normalizeRepoKey(f.remoteUrl).toLowerCase() === group.id) ||
f.path === group.id,
);
const folder = folders.find((f) => folderGroupId(f) === group.id);
const groupFolderId =
folder?.id ?? group.tasks.find((t) => t.folderId)?.folderId;
return (
Expand All @@ -304,30 +303,41 @@ export function TaskListView({
}
}}
newTaskTooltip={`Start new task in ${folder?.name ?? group.name}`}
onContextMenu={
onGroupContextMenu
? (e) => onGroupContextMenu(group.id, e)
: undefined
}
>
{group.tasks.map((task) => (
<TaskRow
key={task.id}
task={task}
isActive={activeTaskId === task.id}
isSelected={selectedIdSet.has(task.id)}
hideHoverActions={hasMultiSelection}
isEditing={editingTaskId === task.id}
onClick={(e) => onTaskClick(task.id, e)}
onDoubleClick={() => onTaskDoubleClick(task.id)}
onContextMenu={(e, isPinned) =>
onTaskContextMenu(task.id, e, isPinned)
}
onArchive={() => onTaskArchive(task.id)}
onTogglePin={() => onTaskTogglePin(task.id)}
onEditSubmit={(newTitle) =>
onTaskEditSubmit(task.id, task.title, newTitle)
}
onEditCancel={onTaskEditCancel}
timestamp={task[timestampKey]}
depth={1}
/>
))}
{group.tasks.length === 0 ? (
<p className="px-4 py-2 text-[12px] text-gray-9">
No agents yet
</p>
) : (
group.tasks.map((task) => (
<TaskRow
key={task.id}
task={task}
isActive={activeTaskId === task.id}
isSelected={selectedIdSet.has(task.id)}
hideHoverActions={hasMultiSelection}
isEditing={editingTaskId === task.id}
onClick={(e) => onTaskClick(task.id, e)}
onDoubleClick={() => onTaskDoubleClick(task.id)}
onContextMenu={(e, isPinned) =>
onTaskContextMenu(task.id, e, isPinned)
}
onArchive={() => onTaskArchive(task.id)}
onTogglePin={() => onTaskTogglePin(task.id)}
onEditSubmit={(newTitle) =>
onTaskEditSubmit(task.id, task.title, newTitle)
}
onEditCancel={onTaskEditCancel}
timestamp={task[timestampKey]}
depth={1}
/>
))
)}
</SidebarSection>
</DraggableFolder>
);
Expand Down
45 changes: 45 additions & 0 deletions packages/ui/src/features/sidebar/components/TasksHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {
FolderPlus,
FunnelSimple as FunnelSimpleIcon,
MagnifyingGlass,
} from "@phosphor-icons/react";
import { useHostTRPCClient } from "@posthog/host-router/react";
import {
Button,
DropdownMenu,
Expand All @@ -13,8 +15,50 @@ import {
MenuLabel,
} from "@posthog/quill";
import { useMeQuery } from "@posthog/ui/features/auth/useMeQuery";
import { useFolders } from "@posthog/ui/features/folders/useFolders";
import { useSidebarStore } from "@posthog/ui/features/sidebar/sidebarStore";
import { Tooltip } from "@posthog/ui/primitives/Tooltip";
import { toast } from "@posthog/ui/primitives/toast";
import { useCommandMenuStore } from "@posthog/ui/shell/commandMenuStore";
import { logger } from "@posthog/ui/shell/logger";
import { useState } from "react";

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

function AddFolderButton() {
const trpcClient = useHostTRPCClient();
const { addFolder } = useFolders();
const [isOpening, setIsOpening] = useState(false);

const handleClick = async () => {
if (isOpening) return;
setIsOpening(true);
try {
const selectedPath = await trpcClient.os.selectDirectory.query();
if (selectedPath) await addFolder(selectedPath);
} catch (error) {
log.error("Failed to add folder", error);
toast.error("Couldn't add folder");
} finally {
setIsOpening(false);
}
};

return (
<Tooltip content="Add folder" side="bottom">
<Button
type="button"
aria-label="Add folder"
size="icon-sm"
onClick={handleClick}
disabled={isOpening}
aria-busy={isOpening}
>
<FolderPlus size={14} />
</Button>
</Tooltip>
);
}

function TaskSearchButton() {
const openCommandMenu = useCommandMenuStore((state) => state.open);
Expand Down Expand Up @@ -131,6 +175,7 @@ export function TasksHeader() {
<MenuLabel className="flex items-center justify-between pt-0 pr-0 pb-0.5">
Tasks
<span className="flex items-center">
<AddFolderButton />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Add folder button visible in modes where it has no visible effect

AddFolderButton is rendered unconditionally, but the added folder only appears as a sidebar group when organizeMode === "by-project". In "Chronological list" mode a user clicking this button will see no change in the UI (there's no success toast and the sidebar layout is unchanged), which makes the action appear broken. The button could be hidden or disabled when not in by-project mode, or a success toast could indicate the folder was added and becomes visible in by-project view.

<TaskSearchButton />
<TaskFilterMenu />
</span>
Expand Down
12 changes: 10 additions & 2 deletions packages/ui/src/features/sidebar/useSidebarData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { computeSummaryIds } from "@posthog/core/sidebar/summaryIds";
import type { AppView } from "@posthog/ui/router/useAppView";
import { useEffect, useMemo, useRef } from "react";
import { useArchivedTaskIds } from "../archive/useArchivedTaskIds";
import { useFolders } from "../folders/useFolders";
import { useProvisioningStore } from "../provisioning/store";
import { useSuspendedTaskIds } from "../suspension/useSuspendedTaskIds";
import { useSlackTasks, useTaskSummaries, useTasks } from "../tasks/useTasks";
Expand Down Expand Up @@ -180,9 +181,16 @@ export function useSidebarData({
[sortedUnpinnedTasks, organizeMode, historyVisibleCount],
);

const { folders } = useFolders();

const groupedTasks = useMemo(
() => groupByRepository(sortedUnpinnedTasks, folderOrder),
[sortedUnpinnedTasks, folderOrder],
() =>
groupByRepository(
sortedUnpinnedTasks,
folderOrder,
organizeMode === "by-project" ? folders : [],
),
[sortedUnpinnedTasks, folderOrder, folders, organizeMode],
);

const groupIdsRef = useRef<string[]>([]);
Expand Down
Loading