feat: sort tasks by creation date in kanban and sidebar (#511)
This commit is contained in:
@@ -129,7 +129,7 @@ func (r *Repository) ListTasks(ctx context.Context, workflowID string) ([]*model
|
||||
SELECT id, workspace_id, workflow_id, workflow_step_id, title, description, state, priority, position, metadata, is_ephemeral, parent_id, archived_at, created_at, updated_at
|
||||
FROM tasks
|
||||
WHERE workflow_id = ? AND archived_at IS NULL AND is_ephemeral = 0
|
||||
ORDER BY position
|
||||
ORDER BY created_at ASC
|
||||
`), workflowID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -164,7 +164,7 @@ func (r *Repository) ListTasksByWorkflowStep(ctx context.Context, workflowStepID
|
||||
rows, err := r.ro.QueryContext(ctx, r.ro.Rebind(`
|
||||
SELECT id, workspace_id, workflow_id, workflow_step_id, title, description, state, priority, position, metadata, is_ephemeral, parent_id, archived_at, created_at, updated_at
|
||||
FROM tasks
|
||||
WHERE workflow_step_id = ? AND archived_at IS NULL AND is_ephemeral = 0 ORDER BY position
|
||||
WHERE workflow_step_id = ? AND archived_at IS NULL AND is_ephemeral = 0 ORDER BY created_at ASC
|
||||
`), workflowStepID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -59,7 +59,7 @@ function getTasksForStep(tasks: Task[], stepId: string) {
|
||||
return tasks
|
||||
.filter((task) => task.workflowStepId === stepId)
|
||||
.map((task) => ({ ...task, position: task.position ?? 0 }))
|
||||
.sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
|
||||
.sort((a, b) => (a.createdAt ?? "").localeCompare(b.createdAt ?? ""));
|
||||
}
|
||||
|
||||
function EmptyState({ showLoading }: { showLoading: boolean }) {
|
||||
|
||||
@@ -59,6 +59,7 @@ export interface Task {
|
||||
isRemoteExecutor?: boolean;
|
||||
parentTaskId?: string | null;
|
||||
updatedAt?: string;
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface WorkflowStep {
|
||||
|
||||
@@ -194,7 +194,7 @@ function useSwimlaneGraphDnd({ tasks, steps, workflowId, onMoveError }: Swimlane
|
||||
for (const col of steps) {
|
||||
map[col.id] = tasks
|
||||
.filter((t) => t.workflowStepId === col.id)
|
||||
.sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
|
||||
.sort((a, b) => (a.createdAt ?? "").localeCompare(b.createdAt ?? ""));
|
||||
}
|
||||
return map;
|
||||
}, [steps, tasks]);
|
||||
|
||||
@@ -79,9 +79,8 @@ function useSwimlaneKanbanDnd({ tasks, workflowId, onMoveError }: SwimlaneKanban
|
||||
.filter(
|
||||
(t: KanbanState["tasks"][number]) => t.workflowStepId === targetStepId && t.id !== taskId,
|
||||
)
|
||||
.sort(
|
||||
(a: KanbanState["tasks"][number], b: KanbanState["tasks"][number]) =>
|
||||
a.position - b.position,
|
||||
.sort((a: KanbanState["tasks"][number], b: KanbanState["tasks"][number]) =>
|
||||
(a.createdAt ?? "").localeCompare(b.createdAt ?? ""),
|
||||
);
|
||||
const nextPosition = targetTasks.length;
|
||||
const originalTasks = snapshot.tasks;
|
||||
@@ -164,7 +163,7 @@ function useTasksByStep(tasks: Task[]) {
|
||||
(stepId: string) =>
|
||||
tasks
|
||||
.filter((t) => t.workflowStepId === stepId)
|
||||
.sort((a, b) => (a.position ?? 0) - (b.position ?? 0)),
|
||||
.sort((a, b) => (a.createdAt ?? "").localeCompare(b.createdAt ?? "")),
|
||||
[tasks],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ export function SwipeableColumns({
|
||||
return tasks
|
||||
.filter((task) => task.workflowStepId === stepId)
|
||||
.map((task) => ({ ...task, position: task.position ?? 0 }))
|
||||
.sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
|
||||
.sort((a, b) => (a.createdAt ?? "").localeCompare(b.createdAt ?? ""));
|
||||
},
|
||||
[tasks],
|
||||
);
|
||||
|
||||
@@ -143,6 +143,7 @@ function toSidebarItem(
|
||||
remoteExecutorName: task.primaryExecutorName ?? undefined,
|
||||
primarySessionId: task.primarySessionId ?? null,
|
||||
updatedAt: sessionInfo.updatedAt ?? task.updatedAt,
|
||||
createdAt: task.createdAt,
|
||||
isArchived: false as boolean,
|
||||
parentTaskTitle: task.parentTaskId ? ctx.titleById.get(task.parentTaskId) : undefined,
|
||||
};
|
||||
@@ -232,6 +233,7 @@ function useSidebarData(workspaceId: string | null) {
|
||||
remoteExecutorName: undefined,
|
||||
primarySessionId: null,
|
||||
updatedAt: archivedState.archivedTaskUpdatedAt,
|
||||
createdAt: undefined,
|
||||
isArchived: true,
|
||||
parentTaskTitle: undefined,
|
||||
});
|
||||
|
||||
@@ -34,6 +34,7 @@ type TaskSwitcherItem = {
|
||||
remoteExecutorType?: string;
|
||||
remoteExecutorName?: string;
|
||||
updatedAt?: string;
|
||||
createdAt?: string;
|
||||
isArchived?: boolean;
|
||||
primarySessionId?: string | null;
|
||||
parentTaskTitle?: string;
|
||||
@@ -206,10 +207,13 @@ export const TaskSwitcher = memo(function TaskSwitcher({
|
||||
else backlog.push(task);
|
||||
}
|
||||
|
||||
const byCreatedAt = (a: TaskSwitcherItem, b: TaskSwitcherItem) =>
|
||||
(a.createdAt ?? "").localeCompare(b.createdAt ?? "");
|
||||
|
||||
return [
|
||||
{ label: "Turn Finished", tasks: review },
|
||||
{ label: "Running", tasks: inProgress },
|
||||
{ label: "Backlog", tasks: backlog },
|
||||
{ label: "Turn Finished", tasks: review.sort(byCreatedAt) },
|
||||
{ label: "Running", tasks: inProgress.sort(byCreatedAt) },
|
||||
{ label: "Backlog", tasks: backlog.sort(byCreatedAt) },
|
||||
] satisfies Section[];
|
||||
}, [tasks]);
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ function mapSnapshotTask(task: Task, stepIds: Set<string>): KanbanTask | null {
|
||||
isRemoteExecutor: task.is_remote_executor ?? false,
|
||||
parentTaskId: task.parent_id ?? undefined,
|
||||
updatedAt: task.updated_at,
|
||||
createdAt: task.created_at,
|
||||
} as KanbanTask;
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ export function useKanbanData({
|
||||
sessionCount: task.sessionCount,
|
||||
reviewStatus: task.reviewStatus,
|
||||
parentTaskId: task.parentTaskId,
|
||||
createdAt: task.createdAt,
|
||||
}));
|
||||
|
||||
const activeSteps = kanban.workflowId ? steps : [];
|
||||
|
||||
@@ -38,6 +38,7 @@ export type KanbanState = {
|
||||
isRemoteExecutor?: boolean;
|
||||
parentTaskId?: string | null;
|
||||
updatedAt?: string;
|
||||
createdAt?: string;
|
||||
}>;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
@@ -11,6 +11,39 @@ function withFallback<T>(value: T | null | undefined, fallback: T | undefined):
|
||||
return value ?? fallback;
|
||||
}
|
||||
|
||||
function buildNullableFields(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
payload: any,
|
||||
existing?: KanbanTask,
|
||||
): Pick<
|
||||
KanbanTask,
|
||||
| "repositoryId"
|
||||
| "primarySessionId"
|
||||
| "primarySessionState"
|
||||
| "sessionCount"
|
||||
| "reviewStatus"
|
||||
| "primaryExecutorId"
|
||||
| "primaryExecutorType"
|
||||
| "primaryExecutorName"
|
||||
| "parentTaskId"
|
||||
| "updatedAt"
|
||||
| "createdAt"
|
||||
> {
|
||||
return {
|
||||
repositoryId: withFallback(payload.repository_id, existing?.repositoryId),
|
||||
primarySessionId: withFallback(payload.primary_session_id, existing?.primarySessionId),
|
||||
primarySessionState: withFallback(payload.primary_session_state, existing?.primarySessionState),
|
||||
sessionCount: withFallback(payload.session_count, existing?.sessionCount),
|
||||
reviewStatus: withFallback(payload.review_status, existing?.reviewStatus),
|
||||
primaryExecutorId: withFallback(payload.primary_executor_id, existing?.primaryExecutorId),
|
||||
primaryExecutorType: withFallback(payload.primary_executor_type, existing?.primaryExecutorType),
|
||||
primaryExecutorName: withFallback(payload.primary_executor_name, existing?.primaryExecutorName),
|
||||
parentTaskId: withFallback(payload.parent_id, existing?.parentTaskId),
|
||||
updatedAt: withFallback(payload.updated_at, existing?.updatedAt),
|
||||
createdAt: withFallback(payload.created_at, existing?.createdAt),
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function buildTaskFromPayload(payload: any, existing?: KanbanTask): KanbanTask {
|
||||
return {
|
||||
@@ -20,17 +53,8 @@ function buildTaskFromPayload(payload: any, existing?: KanbanTask): KanbanTask {
|
||||
description: payload.description,
|
||||
position: payload.position ?? 0,
|
||||
state: payload.state,
|
||||
repositoryId: withFallback(payload.repository_id, existing?.repositoryId),
|
||||
primarySessionId: withFallback(payload.primary_session_id, existing?.primarySessionId),
|
||||
primarySessionState: withFallback(payload.primary_session_state, existing?.primarySessionState),
|
||||
sessionCount: withFallback(payload.session_count, existing?.sessionCount),
|
||||
reviewStatus: withFallback(payload.review_status, existing?.reviewStatus),
|
||||
primaryExecutorId: withFallback(payload.primary_executor_id, existing?.primaryExecutorId),
|
||||
primaryExecutorType: withFallback(payload.primary_executor_type, existing?.primaryExecutorType),
|
||||
primaryExecutorName: withFallback(payload.primary_executor_name, existing?.primaryExecutorName),
|
||||
isRemoteExecutor: payload.is_remote_executor ?? existing?.isRemoteExecutor ?? false,
|
||||
parentTaskId: withFallback(payload.parent_id, existing?.parentTaskId),
|
||||
updatedAt: withFallback(payload.updated_at, existing?.updatedAt),
|
||||
...buildNullableFields(payload, existing),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user