Skip to content
Merged
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
12 changes: 11 additions & 1 deletion apps/sim/app/api/templates/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,17 @@ export const DELETE = withRouteHandler(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

const existing = await db.select().from(templates).where(eq(templates.id, id)).limit(1)
const existing = await db
.select({
name: templates.name,
workflowId: templates.workflowId,
creatorId: templates.creatorId,
status: templates.status,
tags: templates.tags,
})
.from(templates)
.where(eq(templates.id, id))
.limit(1)
if (existing.length === 0) {
logger.warn(`[${requestId}] Template not found for delete: ${id}`)
return NextResponse.json({ error: 'Template not found' }, { status: 404 })
Expand Down
19 changes: 18 additions & 1 deletion apps/sim/app/api/users/me/settings/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,24 @@ export const GET = withRouteHandler(async () => {
}

const userId = session.user.id
const result = await db.select().from(settings).where(eq(settings.userId, userId)).limit(1)
const result = await db
.select({
theme: settings.theme,
autoConnect: settings.autoConnect,
telemetryEnabled: settings.telemetryEnabled,
emailPreferences: settings.emailPreferences,
billingUsageNotificationsEnabled: settings.billingUsageNotificationsEnabled,
showTrainingControls: settings.showTrainingControls,
superUserModeEnabled: settings.superUserModeEnabled,
mothershipEnvironment: settings.mothershipEnvironment,
errorNotificationsEnabled: settings.errorNotificationsEnabled,
snapToGridSize: settings.snapToGridSize,
showActionBar: settings.showActionBar,
lastActiveWorkspaceId: settings.lastActiveWorkspaceId,
})
.from(settings)
.where(eq(settings.userId, userId))
.limit(1)

if (!result.length) {
return NextResponse.json({ data: defaultSettings }, { status: 200 })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ export const GET = withRouteHandler(
try {
if (!isBillingEnabled) {
const [[orgData], [memberCount]] = await Promise.all([
db.select().from(organization).where(eq(organization.id, organizationId)).limit(1),
db
.select({ id: organization.id, name: organization.name })
.from(organization)
.where(eq(organization.id, organizationId))
.limit(1),
db
.select({ count: count() })
.from(member)
Expand Down
17 changes: 16 additions & 1 deletion apps/sim/app/api/v1/admin/organizations/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,22 @@ export const GET = withRouteHandler(
try {
const [countResult, organizations] = await Promise.all([
db.select({ total: count() }).from(organization),
db.select().from(organization).orderBy(organization.name).limit(limit).offset(offset),
db
.select({
id: organization.id,
name: organization.name,
slug: organization.slug,
logo: organization.logo,
orgUsageLimit: organization.orgUsageLimit,
storageUsedBytes: organization.storageUsedBytes,
departedMemberUsage: organization.departedMemberUsage,
createdAt: organization.createdAt,
updatedAt: organization.updatedAt,
})
.from(organization)
.orderBy(organization.name)
.limit(limit)
.offset(offset),
])

const total = countResult[0].total
Expand Down
33 changes: 31 additions & 2 deletions apps/sim/app/api/v1/admin/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,23 @@ export interface AdminWorkflowDetail extends AdminWorkflow {
edgeCount: number
}

export function toAdminWorkflow(dbWorkflow: DbWorkflow): AdminWorkflow {
export type AdminWorkflowSource = Pick<
DbWorkflow,
| 'id'
| 'name'
| 'description'
| 'color'
| 'workspaceId'
| 'folderId'
| 'isDeployed'
| 'deployedAt'
| 'runCount'
| 'lastRunAt'
| 'createdAt'
| 'updatedAt'
>

export function toAdminWorkflow(dbWorkflow: AdminWorkflowSource): AdminWorkflow {
return {
id: dbWorkflow.id,
name: dbWorkflow.name,
Expand Down Expand Up @@ -443,7 +459,20 @@ export interface AdminOrganizationDetail extends AdminOrganization {
subscription: AdminSubscription | null
}

export function toAdminOrganization(dbOrg: DbOrganization): AdminOrganization {
export type AdminOrganizationSource = Pick<
DbOrganization,
| 'id'
| 'name'
| 'slug'
| 'logo'
| 'orgUsageLimit'
| 'storageUsedBytes'
| 'departedMemberUsage'
| 'createdAt'
| 'updatedAt'
>

export function toAdminOrganization(dbOrg: AdminOrganizationSource): AdminOrganization {
return {
id: dbOrg.id,
name: dbOrg.name,
Expand Down
20 changes: 19 additions & 1 deletion apps/sim/app/api/v1/admin/workflows/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,25 @@ export const GET = withRouteHandler(
try {
const [countResult, workflows] = await Promise.all([
db.select({ total: count() }).from(workflow),
db.select().from(workflow).orderBy(workflow.name).limit(limit).offset(offset),
db
.select({
id: workflow.id,
name: workflow.name,
description: workflow.description,
color: workflow.color,
workspaceId: workflow.workspaceId,
folderId: workflow.folderId,
isDeployed: workflow.isDeployed,
deployedAt: workflow.deployedAt,
runCount: workflow.runCount,
lastRunAt: workflow.lastRunAt,
createdAt: workflow.createdAt,
updatedAt: workflow.updatedAt,
})
.from(workflow)
.orderBy(workflow.name)
.limit(limit)
.offset(offset),
])

const total = countResult[0].total
Expand Down
15 changes: 14 additions & 1 deletion apps/sim/app/api/v1/admin/workspaces/[id]/workflows/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,20 @@ export const GET = withRouteHandler(
.from(workflow)
.where(and(eq(workflow.workspaceId, workspaceId), isNull(workflow.archivedAt))),
db
.select()
.select({
id: workflow.id,
name: workflow.name,
description: workflow.description,
color: workflow.color,
workspaceId: workflow.workspaceId,
folderId: workflow.folderId,
isDeployed: workflow.isDeployed,
deployedAt: workflow.deployedAt,
runCount: workflow.runCount,
lastRunAt: workflow.lastRunAt,
createdAt: workflow.createdAt,
updatedAt: workflow.updatedAt,
})
.from(workflow)
.where(and(eq(workflow.workspaceId, workspaceId), isNull(workflow.archivedAt)))
.orderBy(workflow.name)
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ export const DELETE = withRouteHandler(async (request: NextRequest, context: Row
eq(userTableRows.workspaceId, workspaceId)
)
)
.returning()
.returning({ id: userTableRows.id })

if (!deletedRow) {
return NextResponse.json({ error: 'Row not found' }, { status: 404 })
Expand Down
20 changes: 15 additions & 5 deletions apps/sim/app/api/webhooks/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,23 @@ export const PATCH = withRouteHandler(
}
await assertWorkflowMutable(webhookData.workflow.id)

const setClause: Partial<typeof webhook.$inferInsert> = {}
if (isActive !== undefined && isActive !== webhooks[0].webhook.isActive) {
setClause.isActive = isActive
}
if (failedCount !== undefined && failedCount !== webhooks[0].webhook.failedCount) {
setClause.failedCount = failedCount
}

if (Object.keys(setClause).length === 0) {
logger.info(`[${requestId}] No-op webhook PATCH (no field changes): ${id}`)
return NextResponse.json({ webhook: webhooks[0].webhook }, { status: 200 })
}

setClause.updatedAt = new Date()
const updatedWebhook = await db
.update(webhook)
.set({
isActive: isActive !== undefined ? isActive : webhooks[0].webhook.isActive,
failedCount: failedCount !== undefined ? failedCount : webhooks[0].webhook.failedCount,
updatedAt: new Date(),
})
.set(setClause)
.where(eq(webhook.id, id))
.returning()

Expand Down
8 changes: 5 additions & 3 deletions apps/sim/app/workspace/[workspaceId]/logs/logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ import {
const LOGS_PER_PAGE = 50 as const
const SORTABLE_COLUMNS: readonly LogSortBy[] = ['date', 'duration', 'cost', 'status'] as const
const REFRESH_SPINNER_DURATION_MS = 1000 as const
const LIVE_REFRESH_INTERVAL_MS = 10_000 as const
const ACTIVE_RUN_DETAIL_REFRESH_MS = 3_000 as const

const LOG_COLUMNS: ResourceColumn[] = [
{ id: 'workflow', header: 'Workflow' },
Expand Down Expand Up @@ -317,7 +319,7 @@ export default function Logs() {
(query: { state: { data?: WorkflowLogDetail } }) => {
if (!isLive) return false
const status = query.state.data?.status
return status === 'running' || status === 'pending' ? 3000 : false
return status === 'running' || status === 'pending' ? ACTIVE_RUN_DETAIL_REFRESH_MS : false
},
[isLive]
)
Expand Down Expand Up @@ -365,7 +367,7 @@ export default function Logs() {
)

const logsQuery = useLogsList(workspaceId, logFilters, {
refetchInterval: isLive ? 3000 : false,
refetchInterval: isLive ? LIVE_REFRESH_INTERVAL_MS : false,
})

const dashboardFilters = useMemo(
Expand All @@ -383,7 +385,7 @@ export default function Logs() {
)

const dashboardStatsQuery = useDashboardStats(workspaceId, dashboardFilters, {
refetchInterval: isLive ? 3000 : false,
refetchInterval: isLive ? LIVE_REFRESH_INTERVAL_MS : false,
})

const logs = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface PrunedEvent {

const RECONNECT_BACKOFF_MS = [500, 1_000, 2_000, 5_000, 10_000]
const POINTER_PREFIX = 'table-event-stream-pointer:'
const DISPATCH_INVALIDATE_DEBOUNCE_MS = 250

function loadPointer(tableId: string): number {
if (typeof window === 'undefined') return 0
Expand Down Expand Up @@ -73,6 +74,16 @@ export function useTableEventStream({
let lastEventId = loadPointer(tableId)
let reconnectAttempt = 0

// Trailing-edge debounce coalesces window-completion bursts.
let dispatchInvalidateTimer: ReturnType<typeof setTimeout> | null = null
const scheduleDispatchInvalidate = (): void => {
if (dispatchInvalidateTimer !== null) clearTimeout(dispatchInvalidateTimer)
dispatchInvalidateTimer = setTimeout(() => {
dispatchInvalidateTimer = null
void queryClient.invalidateQueries({ queryKey: tableKeys.activeDispatches(tableId) })
}, DISPATCH_INVALIDATE_DEBOUNCE_MS)
}

// Keeps the per-row gutter (`runningByRowId`) live between dispatch events.
// `runningCellCount` (the "X running" badge) is NOT touched here — it's the
// server's dispatch-scope count, seeded optimistically on click and
Expand Down Expand Up @@ -137,9 +148,7 @@ export function useTableEventStream({
if (wasInFlight === null) {
// Row outside the loaded page slice — can't compute the delta locally.
// Refetch the run-state snapshot from the server. Cheap and rare.
void queryClient.invalidateQueries({
queryKey: tableKeys.activeDispatches(tableId),
})
scheduleDispatchInvalidate()
} else {
updateRunningByRow(rowId, wasInFlight, isExecInFlight({ status } as RowExecutionMetadata))
}
Expand Down Expand Up @@ -191,13 +200,13 @@ export function useTableEventStream({
// finish + the cursor advances) and on completion. Re-sync the
// dispatch-scope `runningCellCount` from the server so the badge steps
// down per window and matches a reload exactly.
void queryClient.invalidateQueries({ queryKey: tableKeys.activeDispatches(tableId) })
scheduleDispatchInvalidate()
}

const handlePrune = (payload: PrunedEvent): void => {
logger.info('Table event buffer pruned — full refetch', { tableId, ...payload })
void queryClient.invalidateQueries({ queryKey: tableKeys.rowsRoot(tableId) })
void queryClient.invalidateQueries({ queryKey: tableKeys.activeDispatches(tableId) })
scheduleDispatchInvalidate()
lastEventId = typeof payload.earliestEventId === 'number' ? payload.earliestEventId : 0
savePointer(tableId, lastEventId)
// Close proactively so the server's close doesn't fire onerror and route
Expand Down Expand Up @@ -274,6 +283,7 @@ export function useTableEventStream({
return () => {
cancelled = true
if (reconnectTimer !== null) clearTimeout(reconnectTimer)
if (dispatchInvalidateTimer !== null) clearTimeout(dispatchInvalidateTimer)
eventSource?.close()
eventSource = null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ function WorkflowToolDeployBadge({
workflowId: string
onDeploySuccess?: () => void
}) {
const { data, isLoading } = useDeploymentInfo(workflowId, { refetchOnMount: 'always' })
const { data, isLoading } = useDeploymentInfo(workflowId)
const { mutate, isPending: isDeploying } = useDeployWorkflow()
const userPermissions = useUserPermissionsContext()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ export function useChildWorkflow(
}

const { data, isPending } = useDeploymentInfo(
isWorkflowSelector ? (childWorkflowId ?? null) : null,
{ refetchOnMount: 'always' }
isWorkflowSelector ? (childWorkflowId ?? null) : null
)

const childIsDeployed = data?.isDeployed ?? null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ export function useWand({
})

setTimeout(() => {
queryClient.invalidateQueries({ queryKey: subscriptionKeys.all })
queryClient.invalidateQueries({ queryKey: subscriptionKeys.users() })
}, 1000)
} catch (error: any) {
if (error.name === 'AbortError') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,7 @@ export function useWorkflowExecution() {

// Invalidate subscription queries to update usage
setTimeout(() => {
queryClient.invalidateQueries({ queryKey: subscriptionKeys.all })
queryClient.invalidateQueries({ queryKey: subscriptionKeys.users() })
}, 1000)

safeEnqueue(encodeSSE({ event: 'final', data: result }))
Expand Down Expand Up @@ -1296,7 +1296,7 @@ export function useWorkflowExecution() {
setActiveBlocks(activeWorkflowId, new Set())
}
setTimeout(() => {
queryClient.invalidateQueries({ queryKey: subscriptionKeys.all })
queryClient.invalidateQueries({ queryKey: subscriptionKeys.users() })
}, 1000)
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,8 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {
const handleOperationConfirmed = () => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => {
queryClient.invalidateQueries({ queryKey: subscriptionKeys.all })
}, 1000)
queryClient.invalidateQueries({ queryKey: subscriptionKeys.users() })
}, 5000)
}
onOperationConfirmed(handleOperationConfirmed)
return () => clearTimeout(timeoutId)
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/hooks/queries/subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ interface UseSubscriptionDataOptions {
* @param options - Optional configuration
*/
export function useSubscriptionData(options: UseSubscriptionDataOptions = {}) {
const { includeOrg = false, enabled = true, staleTime = 30 * 1000 } = options
const { includeOrg = false, enabled = true, staleTime = 5 * 60 * 1000 } = options

return useQuery({
queryKey: subscriptionKeys.user(includeOrg),
Expand All @@ -72,7 +72,7 @@ export function prefetchSubscriptionData(queryClient: QueryClient) {
queryClient.prefetchQuery({
queryKey: subscriptionKeys.user(false),
queryFn: ({ signal }) => fetchSubscriptionData(false, signal),
staleTime: 30 * 1000,
staleTime: 5 * 60 * 1000,
})
}

Expand Down
6 changes: 5 additions & 1 deletion apps/sim/lib/copilot/async-runs/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,11 @@ export async function getRunSegment(runId: string) {
'copilot_runs',
{ [TraceAttr.RunId]: runId },
async () => {
const [run] = await db.select().from(copilotRuns).where(eq(copilotRuns.id, runId)).limit(1)
const [run] = await db
.select({ id: copilotRuns.id, userId: copilotRuns.userId })
.from(copilotRuns)
.where(eq(copilotRuns.id, runId))
.limit(1)
return run ?? null
}
)
Expand Down
Loading
Loading