Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
9 changes: 0 additions & 9 deletions apps/sim/app/(auth)/signup/signup-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -359,15 +359,6 @@ function SignupFormContent({
}
}

try {
await client.emailOtp.sendVerificationOtp({
email: emailValue,
type: 'sign-in',
})
} catch (otpErr) {
logger.warn('Failed to send sign-in OTP after signup; user can press Resend', otpErr)
}

router.push('/verify?fromSignup=true')
} catch (error) {
logger.error('Signup error:', error)
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/(auth)/verify/use-verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export function useVerification({

try {
const normalizedEmail = email.trim().toLowerCase()
const response = await client.signIn.emailOtp({
const response = await client.emailOtp.verifyEmail({
email: normalizedEmail,
otp,
})
Expand Down Expand Up @@ -169,7 +169,7 @@ export function useVerification({
client.emailOtp
.sendVerificationOtp({
email: normalizedEmail,
type: 'sign-in',
type: 'email-verification',
})
.then(() => {})
.catch(() => {
Expand Down
10 changes: 3 additions & 7 deletions apps/sim/app/api/logs/stats/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,14 @@ export async function GET(request: NextRequest) {

const totalMs = Math.max(1, endTime.getTime() - startTime.getTime())
const segmentMs = Math.max(60000, Math.floor(totalMs / params.segmentCount))
const startTimeIso = startTime.toISOString()

const statsQuery = await db
.select({
workflowId: workflowExecutionLogs.workflowId,
workflowName: workflow.name,
segmentIndex:
sql<number>`FLOOR(EXTRACT(EPOCH FROM (${workflowExecutionLogs.startedAt} - ${startTime}::timestamp)) * 1000 / ${segmentMs})`.as(
sql<number>`FLOOR(EXTRACT(EPOCH FROM (${workflowExecutionLogs.startedAt} - ${startTimeIso}::timestamp)) * 1000 / ${segmentMs})`.as(
'segment_index'
),
totalExecutions: sql<number>`COUNT(*)`.as('total_executions'),
Expand All @@ -129,12 +130,7 @@ export async function GET(request: NextRequest) {
)
)
.where(whereCondition)
Comment thread
waleedlatif1 marked this conversation as resolved.
.groupBy(
workflowExecutionLogs.workflowId,
workflow.name,
sql`FLOOR(EXTRACT(EPOCH FROM (${workflowExecutionLogs.startedAt} - ${startTime}::timestamp)) * 1000 / ${segmentMs})`
)
.orderBy(workflowExecutionLogs.workflowId, sql`segment_index`)
.groupBy(sql`1, 2, 3`)

const workflowMap = new Map<
string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const updateNotificationSchema = z
levelFilter: levelFilterSchema.optional(),
triggerFilter: triggerFilterSchema.optional(),
includeFinalOutput: z.boolean().optional(),
includeTraceSpans: z.boolean().optional(),
includeRateLimits: z.boolean().optional(),
includeUsageData: z.boolean().optional(),
alertConfig: alertConfigSchema.optional(),
Expand Down Expand Up @@ -146,6 +147,7 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
levelFilter: subscription.levelFilter,
triggerFilter: subscription.triggerFilter,
includeFinalOutput: subscription.includeFinalOutput,
includeTraceSpans: subscription.includeTraceSpans,
includeRateLimits: subscription.includeRateLimits,
includeUsageData: subscription.includeUsageData,
webhookConfig: subscription.webhookConfig,
Expand Down Expand Up @@ -220,6 +222,7 @@ export async function PUT(request: NextRequest, { params }: RouteParams) {
if (data.triggerFilter !== undefined) updateData.triggerFilter = data.triggerFilter
if (data.includeFinalOutput !== undefined)
updateData.includeFinalOutput = data.includeFinalOutput
if (data.includeTraceSpans !== undefined) updateData.includeTraceSpans = data.includeTraceSpans
if (data.includeRateLimits !== undefined) updateData.includeRateLimits = data.includeRateLimits
if (data.includeUsageData !== undefined) updateData.includeUsageData = data.includeUsageData
if (data.alertConfig !== undefined) updateData.alertConfig = data.alertConfig
Expand Down Expand Up @@ -257,6 +260,7 @@ export async function PUT(request: NextRequest, { params }: RouteParams) {
levelFilter: subscription.levelFilter,
triggerFilter: subscription.triggerFilter,
includeFinalOutput: subscription.includeFinalOutput,
includeTraceSpans: subscription.includeTraceSpans,
includeRateLimits: subscription.includeRateLimits,
includeUsageData: subscription.includeUsageData,
webhookConfig: subscription.webhookConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,22 @@ function buildTestPayload(subscription: typeof workspaceNotificationSubscription
data.usage = { currentPeriodCost: 2.45, limit: 20, percentUsed: 12.25, isExceeded: false }
}

if (subscription.includeTraceSpans && subscription.notificationType === 'webhook') {
data.traceSpans = [
{
name: 'test-block',
startTime: timestamp,
endTime: timestamp + 150,
duration: 150,
status: 'success',
blockId: 'block_test_1',
blockType: 'agent',
blockName: 'Test Agent',
children: [],
},
]
}

return { payload, timestamp }
}

Expand Down
5 changes: 4 additions & 1 deletion apps/sim/app/api/workspaces/[id]/notifications/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ const createNotificationSchema = z
levelFilter: levelFilterSchema.default(['info', 'error']),
triggerFilter: triggerFilterSchema.default([...CORE_TRIGGER_TYPES]),
includeFinalOutput: z.boolean().default(false),
includeTraceSpans: z.boolean().default(false),
includeRateLimits: z.boolean().default(false),
includeUsageData: z.boolean().default(false),
alertConfig: alertConfigSchema.optional(),
Expand Down Expand Up @@ -137,6 +138,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
levelFilter: workspaceNotificationSubscription.levelFilter,
triggerFilter: workspaceNotificationSubscription.triggerFilter,
includeFinalOutput: workspaceNotificationSubscription.includeFinalOutput,
includeTraceSpans: workspaceNotificationSubscription.includeTraceSpans,
includeRateLimits: workspaceNotificationSubscription.includeRateLimits,
includeUsageData: workspaceNotificationSubscription.includeUsageData,
webhookConfig: workspaceNotificationSubscription.webhookConfig,
Expand Down Expand Up @@ -220,7 +222,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
}
}

// Encrypt webhook secret if provided
let webhookConfig = data.webhookConfig || null
if (webhookConfig?.secret) {
const { encrypted } = await encryptSecret(webhookConfig.secret)
Expand All @@ -238,6 +239,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
levelFilter: data.levelFilter,
triggerFilter: data.triggerFilter,
includeFinalOutput: data.includeFinalOutput,
includeTraceSpans: data.includeTraceSpans,
includeRateLimits: data.includeRateLimits,
includeUsageData: data.includeUsageData,
alertConfig: data.alertConfig || null,
Expand All @@ -263,6 +265,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
levelFilter: subscription.levelFilter,
triggerFilter: subscription.triggerFilter,
includeFinalOutput: subscription.includeFinalOutput,
includeTraceSpans: subscription.includeTraceSpans,
includeRateLimits: subscription.includeRateLimits,
includeUsageData: subscription.includeUsageData,
webhookConfig: subscription.webhookConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export function NotificationSettings({
levelFilter: ['info', 'error'] as LogLevel[],
triggerFilter: [...CORE_TRIGGER_TYPES] as CoreTriggerType[],
includeFinalOutput: false,
includeTraceSpans: false,
includeRateLimits: false,
includeUsageData: false,
webhookUrl: '',
Expand Down Expand Up @@ -202,6 +203,7 @@ export function NotificationSettings({
levelFilter: ['info', 'error'],
triggerFilter: [...CORE_TRIGGER_TYPES],
includeFinalOutput: false,
includeTraceSpans: false,
includeRateLimits: false,
includeUsageData: false,
webhookUrl: '',
Expand Down Expand Up @@ -420,6 +422,8 @@ export function NotificationSettings({
levelFilter: formData.levelFilter,
triggerFilter: formData.triggerFilter,
includeFinalOutput: formData.includeFinalOutput,
// Trace spans only available for webhooks (too large for email/Slack)
includeTraceSpans: activeTab === 'webhook' ? formData.includeTraceSpans : false,
includeRateLimits: formData.includeRateLimits,
includeUsageData: formData.includeUsageData,
alertConfig,
Expand Down Expand Up @@ -471,6 +475,7 @@ export function NotificationSettings({
levelFilter: subscription.levelFilter as LogLevel[],
triggerFilter: subscription.triggerFilter as CoreTriggerType[],
includeFinalOutput: subscription.includeFinalOutput,
includeTraceSpans: subscription.includeTraceSpans,
includeRateLimits: subscription.includeRateLimits,
includeUsageData: subscription.includeUsageData,
webhookUrl: subscription.webhookConfig?.url || '',
Expand Down Expand Up @@ -826,13 +831,18 @@ export function NotificationSettings({
<Combobox
options={[
{ label: 'Final Output', value: 'includeFinalOutput' },
// Trace spans only available for webhooks (too large for email/Slack)
...(activeTab === 'webhook'
? [{ label: 'Trace Spans', value: 'includeTraceSpans' }]
: []),
{ label: 'Rate Limits', value: 'includeRateLimits' },
{ label: 'Usage Data', value: 'includeUsageData' },
]}
multiSelect
multiSelectValues={
[
formData.includeFinalOutput && 'includeFinalOutput',
formData.includeTraceSpans && activeTab === 'webhook' && 'includeTraceSpans',
formData.includeRateLimits && 'includeRateLimits',
formData.includeUsageData && 'includeUsageData',
].filter(Boolean) as string[]
Expand All @@ -841,6 +851,7 @@ export function NotificationSettings({
setFormData({
...formData,
includeFinalOutput: values.includes('includeFinalOutput'),
includeTraceSpans: values.includes('includeTraceSpans'),
includeRateLimits: values.includes('includeRateLimits'),
includeUsageData: values.includes('includeUsageData'),
})
Expand All @@ -849,11 +860,13 @@ export function NotificationSettings({
overlayContent={(() => {
const labels: Record<string, string> = {
includeFinalOutput: 'Final Output',
includeTraceSpans: 'Trace Spans',
includeRateLimits: 'Rate Limits',
includeUsageData: 'Usage Data',
}
const selected = [
formData.includeFinalOutput && 'includeFinalOutput',
formData.includeTraceSpans && activeTab === 'webhook' && 'includeTraceSpans',
formData.includeRateLimits && 'includeRateLimits',
formData.includeUsageData && 'includeUsageData',
].filter(Boolean) as string[]
Expand Down
12 changes: 11 additions & 1 deletion apps/sim/background/workspace-notification-delivery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
import { RateLimiter } from '@/lib/core/rate-limiter'
import { decryptSecret } from '@/lib/core/security/encryption'
import { getBaseUrl } from '@/lib/core/utils/urls'
import type { WorkflowExecutionLog } from '@/lib/logs/types'
import type { TraceSpan, WorkflowExecutionLog } from '@/lib/logs/types'
import { sendEmail } from '@/lib/messaging/email/mailer'
import type { AlertConfig } from '@/lib/notifications/alert-rules'

Expand Down Expand Up @@ -50,6 +50,7 @@ interface NotificationPayload {
totalDurationMs: number
cost?: Record<string, unknown>
finalOutput?: unknown
traceSpans?: TraceSpan[]
rateLimits?: EmailRateLimitsData
usage?: EmailUsageData
}
Expand Down Expand Up @@ -98,6 +99,15 @@ async function buildPayload(
payload.data.finalOutput = executionData.finalOutput
}

// Trace spans only included for webhooks (too large for email/Slack)
if (
subscription.includeTraceSpans &&
subscription.notificationType === 'webhook' &&
executionData.traceSpans
) {
payload.data.traceSpans = executionData.traceSpans as TraceSpan[]
}

if (subscription.includeRateLimits && userId) {
try {
const userSubscription = await getHighestPrioritySubscription(userId)
Expand Down
12 changes: 10 additions & 2 deletions apps/sim/components/emails/auth/welcome-email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,29 @@ export function WelcomeEmail({ userName }: WelcomeEmailProps) {
workflows in minutes.
</Text>

<Link href={`${baseUrl}/w`} style={{ textDecoration: 'none' }}>
<Link href={`${baseUrl}/login`} style={{ textDecoration: 'none' }}>
<Text style={baseStyles.button}>Get Started</Text>
</Link>

<Text style={baseStyles.paragraph}>
If you have any questions or feedback, just reply to this email. I read every message!
</Text>

<Text style={baseStyles.paragraph}>
Want to chat?{' '}
<Link href={`${baseUrl}/team`} style={baseStyles.link}>
Schedule a call
</Link>{' '}
with our team.
</Text>

<Text style={baseStyles.paragraph}>- Emir, co-founder of {brand.name}</Text>

{/* Divider */}
<div style={baseStyles.divider} />

<Text style={{ ...baseStyles.footerText, textAlign: 'left' }}>
You're on the free plan with $10 in credits to get started.
You're on the free plan with $20 in credits to get started.
</Text>
</EmailLayout>
)
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/components/emails/billing/plan-welcome-email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function PlanWelcomeEmail({ planName, userName, loginLink }: PlanWelcomeE

<Text style={baseStyles.paragraph}>
Want help getting started?{' '}
<Link href='https://cal.com/emirkarabeg/sim-team' style={baseStyles.link}>
<Link href={`${baseUrl}/team`} style={baseStyles.link}>
Schedule a call
</Link>{' '}
with our team.
Expand Down
65 changes: 7 additions & 58 deletions apps/sim/hooks/queries/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,68 +154,18 @@ export function useLogDetail(logId: string | undefined) {
})
}

interface DashboardFilters {
timeRange: TimeRange
startDate?: string
endDate?: string
level: string
workflowIds: string[]
folderIds: string[]
triggers: string[]
searchQuery: string
segmentCount?: number
}

/**
* Fetches aggregated dashboard statistics from the server.
* Uses SQL aggregation for efficient computation without row limits.
* Fetches dashboard stats from the server-side aggregation endpoint.
* Uses SQL aggregation for efficient computation without arbitrary limits.
*/
async function fetchDashboardStats(
workspaceId: string,
filters: DashboardFilters
filters: Omit<LogFilters, 'limit'>
): Promise<DashboardStatsResponse> {
const params = new URLSearchParams()

params.set('workspaceId', workspaceId)

if (filters.segmentCount) {
params.set('segmentCount', filters.segmentCount.toString())
}

if (filters.level !== 'all') {
params.set('level', filters.level)
}

if (filters.triggers.length > 0) {
params.set('triggers', filters.triggers.join(','))
}

if (filters.workflowIds.length > 0) {
params.set('workflowIds', filters.workflowIds.join(','))
}

if (filters.folderIds.length > 0) {
params.set('folderIds', filters.folderIds.join(','))
}

const startDate = getStartDateFromTimeRange(filters.timeRange, filters.startDate)
if (startDate) {
params.set('startDate', startDate.toISOString())
}

const endDate = getEndDateFromTimeRange(filters.timeRange, filters.endDate)
if (endDate) {
params.set('endDate', endDate.toISOString())
}

if (filters.searchQuery.trim()) {
const parsedQuery = parseQuery(filters.searchQuery.trim())
const searchParams = queryToApiParams(parsedQuery)

for (const [key, value] of Object.entries(searchParams)) {
params.set(key, value)
}
}
applyFilterParams(params, filters)

const response = await fetch(`/api/logs/stats?${params.toString()}`)

Expand All @@ -232,13 +182,12 @@ interface UseDashboardStatsOptions {
}

/**
* Hook for fetching aggregated dashboard statistics.
* Uses server-side SQL aggregation for efficient computation
* without any row limits - all matching logs are included in the stats.
* Hook for fetching dashboard stats using server-side aggregation.
* No arbitrary limits - uses SQL aggregation for accurate metrics.
*/
export function useDashboardStats(
workspaceId: string | undefined,
filters: DashboardFilters,
filters: Omit<LogFilters, 'limit'>,
options?: UseDashboardStatsOptions
) {
return useQuery({
Expand Down
Loading
Loading