|
1 | | -import { workflow, workflowExecutionLogs } from '@sim/db/schema' |
2 | | -import { and, eq, gt, gte, inArray, lt, lte, ne, type SQL, sql } from 'drizzle-orm' |
| 1 | +import { db } from '@sim/db' |
| 2 | +import { workflow, workflowExecutionLogs, workflowFolder } from '@sim/db/schema' |
| 3 | +import { and, eq, gt, gte, inArray, isNull, lt, lte, ne, type SQL, sql } from 'drizzle-orm' |
3 | 4 | import { z } from 'zod' |
4 | 5 | import type { TimeRange } from '@/stores/logs/filters/types' |
5 | 6 |
|
@@ -128,6 +129,53 @@ function buildWorkflowIdsCondition(workflowIds: string): SQL | undefined { |
128 | 129 | return undefined |
129 | 130 | } |
130 | 131 |
|
| 132 | +/** |
| 133 | + * Expands a CSV of selected folder IDs to include every descendant folder in the |
| 134 | + * workspace, so that filtering by a parent folder also matches workflows that |
| 135 | + * live in nested subfolders. |
| 136 | + * |
| 137 | + * Returns the original CSV when there are no descendants (or when the input is |
| 138 | + * empty / undefined). Unknown IDs are preserved so the caller's `inArray` check |
| 139 | + * behaves the same as today (matches nothing). |
| 140 | + */ |
| 141 | +export async function expandFolderIdsWithDescendants( |
| 142 | + workspaceId: string, |
| 143 | + folderIdsCsv: string | undefined |
| 144 | +): Promise<string | undefined> { |
| 145 | + if (!folderIdsCsv) return folderIdsCsv |
| 146 | + const seedIds = folderIdsCsv.split(',').filter(Boolean) |
| 147 | + if (seedIds.length === 0) return folderIdsCsv |
| 148 | + |
| 149 | + const rows = await db |
| 150 | + .select({ id: workflowFolder.id, parentId: workflowFolder.parentId }) |
| 151 | + .from(workflowFolder) |
| 152 | + .where(and(eq(workflowFolder.workspaceId, workspaceId), isNull(workflowFolder.archivedAt))) |
| 153 | + |
| 154 | + const childrenByParent = new Map<string, string[]>() |
| 155 | + for (const row of rows) { |
| 156 | + if (!row.parentId) continue |
| 157 | + const list = childrenByParent.get(row.parentId) |
| 158 | + if (list) list.push(row.id) |
| 159 | + else childrenByParent.set(row.parentId, [row.id]) |
| 160 | + } |
| 161 | + |
| 162 | + const expanded = new Set<string>(seedIds) |
| 163 | + const queue = [...seedIds] |
| 164 | + while (queue.length > 0) { |
| 165 | + const current = queue.shift() as string |
| 166 | + const children = childrenByParent.get(current) |
| 167 | + if (!children) continue |
| 168 | + for (const childId of children) { |
| 169 | + if (!expanded.has(childId)) { |
| 170 | + expanded.add(childId) |
| 171 | + queue.push(childId) |
| 172 | + } |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + return Array.from(expanded).join(',') |
| 177 | +} |
| 178 | + |
131 | 179 | function buildFolderIdsCondition(folderIds: string): SQL | undefined { |
132 | 180 | const ids = folderIds.split(',').filter(Boolean) |
133 | 181 | if (ids.length > 0) { |
|
0 commit comments