Skip to content

Commit c749b1b

Browse files
committed
fix initial ordering issue
1 parent 2cd09da commit c749b1b

File tree

5 files changed

+95
-20
lines changed

5 files changed

+95
-20
lines changed

apps/sim/app/api/workflows/route.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { db } from '@sim/db'
22
import { workflow } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
4-
import { and, asc, eq, isNull, max } from 'drizzle-orm'
4+
import { and, asc, eq, isNull, min } from 'drizzle-orm'
55
import { type NextRequest, NextResponse } from 'next/server'
66
import { z } from 'zod'
77
import { getSession } from '@/lib/auth'
@@ -150,15 +150,15 @@ export async function POST(req: NextRequest) {
150150
sortOrder = providedSortOrder
151151
} else {
152152
const folderCondition = folderId ? eq(workflow.folderId, folderId) : isNull(workflow.folderId)
153-
const [maxResult] = await db
154-
.select({ maxOrder: max(workflow.sortOrder) })
153+
const [minResult] = await db
154+
.select({ minOrder: min(workflow.sortOrder) })
155155
.from(workflow)
156156
.where(
157157
workspaceId
158158
? and(eq(workflow.workspaceId, workspaceId), folderCondition)
159159
: and(eq(workflow.userId, session.user.id), folderCondition)
160160
)
161-
sortOrder = (maxResult?.maxOrder ?? -1) + 1
161+
sortOrder = (minResult?.minOrder ?? 1) - 1
162162
}
163163

164164
await db.insert(workflow).values({

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/workflow-list.tsx

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ const TREE_SPACING = {
1818
INDENT_PER_LEVEL: 20,
1919
} as const
2020

21+
function compareByOrder<T extends { sortOrder: number; createdAt?: Date; id: string }>(
22+
a: T,
23+
b: T
24+
): number {
25+
if (a.sortOrder !== b.sortOrder) return a.sortOrder - b.sortOrder
26+
const timeA = a.createdAt?.getTime() ?? 0
27+
const timeB = b.createdAt?.getTime() ?? 0
28+
if (timeA !== timeB) return timeA - timeB
29+
return a.id.localeCompare(b.id)
30+
}
31+
2132
interface WorkflowListProps {
2233
regularWorkflows: WorkflowMetadata[]
2334
isLoading?: boolean
@@ -97,7 +108,7 @@ export function WorkflowList({
97108
{} as Record<string, WorkflowMetadata[]>
98109
)
99110
for (const folderId of Object.keys(grouped)) {
100-
grouped[folderId].sort((a, b) => a.sortOrder - b.sortOrder)
111+
grouped[folderId].sort(compareByOrder)
101112
}
102113
return grouped
103114
}, [regularWorkflows])
@@ -226,7 +237,15 @@ export function WorkflowList({
226237
data: workflow,
227238
})
228239
}
229-
childItems.sort((a, b) => a.sortOrder - b.sortOrder)
240+
childItems.sort((a, b) => {
241+
if (a.sortOrder !== b.sortOrder) return a.sortOrder - b.sortOrder
242+
const dataA = a.data as { createdAt?: Date }
243+
const dataB = b.data as { createdAt?: Date }
244+
const timeA = dataA.createdAt?.getTime() ?? 0
245+
const timeB = dataB.createdAt?.getTime() ?? 0
246+
if (timeA !== timeB) return timeA - timeB
247+
return a.id.localeCompare(b.id)
248+
})
230249

231250
return (
232251
<div key={folder.id} className='relative'>
@@ -307,7 +326,15 @@ export function WorkflowList({
307326
data: workflow,
308327
})
309328
}
310-
return items.sort((a, b) => a.sortOrder - b.sortOrder)
329+
return items.sort((a, b) => {
330+
if (a.sortOrder !== b.sortOrder) return a.sortOrder - b.sortOrder
331+
const dataA = a.data as { createdAt?: Date }
332+
const dataB = b.data as { createdAt?: Date }
333+
const timeA = dataA.createdAt?.getTime() ?? 0
334+
const timeB = dataB.createdAt?.getTime() ?? 0
335+
if (timeA !== timeB) return timeA - timeB
336+
return a.id.localeCompare(b.id)
337+
})
311338
}, [folderTree, rootWorkflows])
312339

313340
const hasRootItems = rootItems.length > 0

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-drag-drop.ts

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,20 @@ export function useDragDrop() {
133133
[]
134134
)
135135

136-
type SiblingItem = { type: 'folder' | 'workflow'; id: string; sortOrder: number }
136+
type SiblingItem = {
137+
type: 'folder' | 'workflow'
138+
id: string
139+
sortOrder: number
140+
createdAt: Date
141+
}
142+
143+
const compareSiblingItems = (a: SiblingItem, b: SiblingItem): number => {
144+
if (a.sortOrder !== b.sortOrder) return a.sortOrder - b.sortOrder
145+
const timeA = a.createdAt.getTime()
146+
const timeB = b.createdAt.getTime()
147+
if (timeA !== timeB) return timeA - timeB
148+
return a.id.localeCompare(b.id)
149+
}
137150

138151
const getDestinationFolderId = useCallback((indicator: DropIndicator): string | null => {
139152
return indicator.position === 'inside'
@@ -202,11 +215,21 @@ export function useDragDrop() {
202215
return [
203216
...Object.values(currentFolders)
204217
.filter((f) => f.parentId === folderId)
205-
.map((f) => ({ type: 'folder' as const, id: f.id, sortOrder: f.sortOrder })),
218+
.map((f) => ({
219+
type: 'folder' as const,
220+
id: f.id,
221+
sortOrder: f.sortOrder,
222+
createdAt: f.createdAt,
223+
})),
206224
...Object.values(currentWorkflows)
207225
.filter((w) => w.folderId === folderId)
208-
.map((w) => ({ type: 'workflow' as const, id: w.id, sortOrder: w.sortOrder })),
209-
].sort((a, b) => a.sortOrder - b.sortOrder)
226+
.map((w) => ({
227+
type: 'workflow' as const,
228+
id: w.id,
229+
sortOrder: w.sortOrder,
230+
createdAt: w.createdAt,
231+
})),
232+
].sort(compareSiblingItems)
210233
}, [])
211234

212235
const setNormalizedDropIndicator = useCallback(
@@ -299,8 +322,9 @@ export function useDragDrop() {
299322
type: 'workflow' as const,
300323
id,
301324
sortOrder: currentWorkflows[id]?.sortOrder ?? 0,
325+
createdAt: currentWorkflows[id]?.createdAt ?? new Date(),
302326
}))
303-
.sort((a, b) => a.sortOrder - b.sortOrder)
327+
.sort(compareSiblingItems)
304328

305329
const insertAt = calculateInsertIndex(remaining, indicator)
306330

@@ -369,7 +393,12 @@ export function useDragDrop() {
369393

370394
const newOrder: SiblingItem[] = [
371395
...remaining.slice(0, insertAt),
372-
{ type: 'folder', id: draggedFolderId, sortOrder: 0 },
396+
{
397+
type: 'folder',
398+
id: draggedFolderId,
399+
sortOrder: 0,
400+
createdAt: draggedFolder?.createdAt ?? new Date(),
401+
},
373402
...remaining.slice(insertAt),
374403
]
375404

apps/sim/hooks/queries/workflows.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ export function useCreateWorkflow() {
194194
const workflowsInFolder = Object.values(currentWorkflows).filter(
195195
(w) => w.folderId === targetFolderId
196196
)
197-
sortOrder = workflowsInFolder.reduce((max, w) => Math.max(max, w.sortOrder ?? 0), -1) + 1
197+
sortOrder = workflowsInFolder.reduce((min, w) => Math.min(min, w.sortOrder ?? 0), 1) - 1
198198
}
199199

200200
return {
@@ -294,7 +294,7 @@ export function useDuplicateWorkflowMutation() {
294294
const workflowsInFolder = Object.values(currentWorkflows).filter(
295295
(w) => w.folderId === targetFolderId
296296
)
297-
const maxSortOrder = workflowsInFolder.reduce((max, w) => Math.max(max, w.sortOrder ?? 0), -1)
297+
const minSortOrder = workflowsInFolder.reduce((min, w) => Math.min(min, w.sortOrder ?? 0), 1)
298298

299299
return {
300300
id: tempId,
@@ -305,7 +305,7 @@ export function useDuplicateWorkflowMutation() {
305305
color: variables.color,
306306
workspaceId: variables.workspaceId,
307307
folderId: targetFolderId,
308-
sortOrder: maxSortOrder + 1,
308+
sortOrder: minSortOrder - 1,
309309
}
310310
}
311311
)

apps/sim/lib/workflows/persistence/duplicate.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { db } from '@sim/db'
22
import { workflow, workflowBlocks, workflowEdges, workflowSubflows } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
4-
import { eq } from 'drizzle-orm'
4+
import { and, eq, isNull, min } from 'drizzle-orm'
55
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
66
import type { Variable } from '@/stores/panel/variables/types'
77
import type { LoopConfig, ParallelConfig } from '@/stores/workflows/workflow/types'
@@ -26,6 +26,7 @@ interface DuplicateWorkflowResult {
2626
color: string
2727
workspaceId: string
2828
folderId: string | null
29+
sortOrder: number
2930
blocksCount: number
3031
edgesCount: number
3132
subflowsCount: number
@@ -88,12 +89,29 @@ export async function duplicateWorkflow(
8889
throw new Error('Source workflow not found or access denied')
8990
}
9091

92+
const targetWorkspaceId = workspaceId || source.workspaceId
93+
const targetFolderId = folderId !== undefined ? folderId : source.folderId
94+
const folderCondition = targetFolderId
95+
? eq(workflow.folderId, targetFolderId)
96+
: isNull(workflow.folderId)
97+
98+
const [minResult] = await tx
99+
.select({ minOrder: min(workflow.sortOrder) })
100+
.from(workflow)
101+
.where(
102+
targetWorkspaceId
103+
? and(eq(workflow.workspaceId, targetWorkspaceId), folderCondition)
104+
: and(eq(workflow.userId, userId), folderCondition)
105+
)
106+
const sortOrder = (minResult?.minOrder ?? 1) - 1
107+
91108
// Create the new workflow first (required for foreign key constraints)
92109
await tx.insert(workflow).values({
93110
id: newWorkflowId,
94111
userId,
95-
workspaceId: workspaceId || source.workspaceId,
96-
folderId: folderId !== undefined ? folderId : source.folderId,
112+
workspaceId: targetWorkspaceId,
113+
folderId: targetFolderId,
114+
sortOrder,
97115
name,
98116
description: description || source.description,
99117
color: color || source.color,
@@ -286,7 +304,8 @@ export async function duplicateWorkflow(
286304
description: description || source.description,
287305
color: color || source.color,
288306
workspaceId: finalWorkspaceId,
289-
folderId: folderId !== undefined ? folderId : source.folderId,
307+
folderId: targetFolderId,
308+
sortOrder,
290309
blocksCount: sourceBlocks.length,
291310
edgesCount: sourceEdges.length,
292311
subflowsCount: sourceSubflows.length,

0 commit comments

Comments
 (0)