Skip to content
Open
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
38 changes: 38 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,36 @@ 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);
toast.error("Couldn't perform folder action");
}
Comment thread
MattPua marked this conversation as resolved.
},
[folders, removeFolder, hostClient, openExternalApp],
);

const handleTaskContextMenu = (
taskId: string,
e: React.MouseEvent,
Expand Down Expand Up @@ -431,6 +468,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 tasks 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
44 changes: 44 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,49 @@ 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}
>
<FolderPlus size={14} />
</Button>
</Tooltip>
);
}

function TaskSearchButton() {
const openCommandMenu = useCommandMenuStore((state) => state.open);
Expand Down Expand Up @@ -131,6 +174,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 />
<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