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
38 changes: 38 additions & 0 deletions apps/web/src/actions/admin/weeklyEmail/enqueueWeeklyEmail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use server'

import { withAdmin } from '../../procedures'
import { z } from 'zod'
import { queues } from '@latitude-data/core/queues'

export const enqueueWeeklyEmailAction = withAdmin
.inputSchema(
z.object({
workspaceId: z.number(),
emails: z.string().optional(),
}),
)
.action(async ({ parsedInput }) => {
const { workspaceId, emails } = parsedInput

// Parse comma-separated emails if provided
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove ai comments when possible

const emailList = emails
? emails
.split(',')
.map((e) => e.trim())
.filter((e) => e.length > 0)
: undefined

const { maintenanceQueue } = await queues()
await maintenanceQueue.add(
'sendWeeklyEmailJob',
{
workspaceId,
emails: emailList,
},
{
jobId: `weekly-email-manual-${workspaceId}-${Date.now()}`,
},
)

return { success: true }
})
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ export function BackofficeTabs({ children }: { children: ReactNode }) {
value: BackofficeRoutes.integrations,
route: ROUTES.backoffice.integrations.root,
},
{
label: 'Weekly',
value: BackofficeRoutes.weekly,
route: ROUTES.backoffice.weekly.root,
},
]}
selected={selected}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ export function WorkspaceDashboard({ workspace }: Props) {
issuesUnlocked={workspace.issuesUnlocked}
/>
<div className='flex gap-2'>
<Link
href={ROUTES.backoffice[BackofficeRoutes.weekly].withWorkspaceId(
workspace.id,
)}
>
<Button fancy variant='outline'>
<Text.H5B noWrap>See Weekly</Text.H5B>
</Button>
</Link>
<ChangePlanButton
workspaceId={workspace.id}
currentPlan={workspace.subscription.plan}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
'use client'

import { FormEvent, useCallback, useEffect, useState } from 'react'
import { useSearchParams } from 'next/navigation'
import { Button } from '@latitude-data/web-ui/atoms/Button'
import { Input } from '@latitude-data/web-ui/atoms/Input'
import { TextArea } from '@latitude-data/web-ui/atoms/TextArea'
import { Text } from '@latitude-data/web-ui/atoms/Text'
import { useToast } from '@latitude-data/web-ui/atoms/Toast'
import { enqueueWeeklyEmailAction } from '$/actions/admin/weeklyEmail/enqueueWeeklyEmail'
import useLatitudeAction from '$/hooks/useLatitudeAction'

export function WeeklyEmailForm() {
const searchParams = useSearchParams()
const { toast } = useToast()
const [workspaceId, setWorkspaceId] = useState('')
const [emails, setEmails] = useState('')

// Pre-fill workspaceId from query params if present
useEffect(() => {
const workspaceIdParam = searchParams.get('workspaceId')
if (workspaceIdParam) {
setWorkspaceId(workspaceIdParam)
}
}, [searchParams])

const { execute: enqueueEmail, isPending } = useLatitudeAction(
enqueueWeeklyEmailAction,
{
onSuccess: () => {
toast({
title: 'Success',
description: 'Weekly email job has been enqueued successfully',
})
// Clear form
setWorkspaceId('')
setEmails('')
},
onError: (error) => {
toast({
title: 'Error',
description: error.message,
variant: 'destructive',
})
},
},
)

const handleSubmit = useCallback(
(e: FormEvent) => {
e.preventDefault()

const parsedWorkspaceId = parseInt(workspaceId)
if (isNaN(parsedWorkspaceId)) {
toast({
title: 'Invalid Workspace ID',
description: 'Please enter a valid numeric workspace ID',
variant: 'destructive',
})
return
}

enqueueEmail({
workspaceId: parsedWorkspaceId,
emails: emails.trim() || undefined,
})
},
[workspaceId, emails, enqueueEmail, toast],
)

return (
<form onSubmit={handleSubmit} className='flex flex-col gap-y-4 max-w-2xl'>
<div className='flex flex-col gap-y-2'>
<Text.H5 weight='medium'>Workspace ID</Text.H5>
<Input
type='number'
placeholder='Enter workspace ID'
value={workspaceId}
onChange={(e) => setWorkspaceId(e.target.value)}
required
/>
<Text.H6 color='foregroundMuted'>
The ID of the workspace to send the weekly email for
</Text.H6>
</div>

<div className='flex flex-col gap-y-2'>
<Text.H5 weight='medium'>Email Addresses (Optional)</Text.H5>
<TextArea
placeholder='email1@example.com, email2@example.com'
value={emails}
onChange={(e) => setEmails(e.target.value)}
rows={4}
/>
<Text.H6 color='foregroundMuted'>
Comma-separated list of email addresses. If left empty, the email will
be sent to all workspace members who have opted in to receive weekly
emails.
</Text.H6>
</div>

<div className='flex gap-2'>
<Button type='submit' fancy disabled={isPending}>
{isPending ? 'Enqueueing...' : 'Enqueue Weekly Email'}
</Button>
</div>
</form>
)
}
20 changes: 20 additions & 0 deletions apps/web/src/app/(admin)/backoffice/weekly/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client'

import { Text } from '@latitude-data/web-ui/atoms/Text'
import { WeeklyEmailForm } from './_components/WeeklyEmailForm'

export default function AdminWeeklyEmail() {
return (
<div className='container flex flex-col gap-y-8'>
<section className='flex flex-col gap-y-4'>
<Text.H1>Weekly Email</Text.H1>
<Text.H4 color='foregroundMuted'>
Manually trigger weekly email reports for specific workspaces. You can
optionally provide a comma-separated list of email addresses to send
the report to instead of the workspace members.
</Text.H4>
<WeeklyEmailForm />
</section>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

import { useMemo } from 'react'
import { formatCount } from '$/lib/formatCount'
import { formatCount } from '@latitude-data/constants/formatCount'
import { ROUTES } from '$/services/routes'

import { RunSourceGroup } from '@latitude-data/constants'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { formatCostInMillicents } from '$/app/_lib/formatUtils'
import { useCurrentEvaluationV2 } from '$/app/providers/EvaluationV2Provider'
import { EVALUATION_SPECIFICATIONS } from '$/components/evaluations'
import { formatCount } from '$/lib/formatCount'
import { formatCount } from '@latitude-data/constants/formatCount'
import { ChartBlankSlate } from '@latitude-data/web-ui/atoms/ChartBlankSlate'
import { Text } from '@latitude-data/web-ui/atoms/Text'
import { AreaChart, ChartWrapper } from '@latitude-data/web-ui/molecules/Charts'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { formatCostInMillicents } from '$/app/_lib/formatUtils'
import { useCurrentEvaluationV2 } from '$/app/providers/EvaluationV2Provider'
import { EVALUATION_SPECIFICATIONS } from '$/components/evaluations'
import { formatCount } from '$/lib/formatCount'
import { formatCount } from '@latitude-data/constants/formatCount'
import { Badge } from '@latitude-data/web-ui/atoms/Badge'
import { ChartBlankSlate } from '@latitude-data/web-ui/atoms/ChartBlankSlate'
import { Text } from '@latitude-data/web-ui/atoms/Text'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { useCurrentProject } from '$/app/providers/ProjectProvider'
import { DurationCell } from './DurationCell'
import { ScoreCell } from './ScoreCell'
import { cn } from '@latitude-data/web-ui/utils'
import { formatCount } from '$/lib/formatCount'
import { formatCount } from '@latitude-data/constants/formatCount'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird that constants exposes a function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But is a really nice method and I want to share it between apps/web and packages/email. Constants is the right place. Constants could have been called in other way. I agree on that.

import { useSearchParams } from 'next/navigation'
import { LinkableTablePaginationFooter } from '$/components/TablePaginationFooter'
import { DocumentRoutes, ROUTES } from '$/services/routes'
Expand Down
8 changes: 2 additions & 6 deletions apps/web/src/app/api/spans/limited/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ export const GET = errorHandler(
: null

let spansResult
const spansRepository = new SpansRepository(workspace.id)

if (filters.traceId) {
// If traceId is present in filters, fetch spans for that specific trace
const spansRepository = new SpansRepository(workspace.id)
const spans = await spansRepository
.list({ traceId: filters.traceId })
.then((r) =>
Expand All @@ -70,13 +70,9 @@ export const GET = errorHandler(
next: null,
}
} else {
// Otherwise, fetch spans directly from the repository
const spansRepository = new SpansRepository(workspace.id)

let result: { items: any[]; next: any } = { items: [], next: null }

if (documentUuid) {
// Document queries require commitUuid
if (!commitUuid) {
return NextResponse.json(
{ error: 'commitUuid is required when documentUuid is provided' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ function WhyStep() {
<div className='flex flex-col gap-y-2'>
<div className='flex items-center gap-2'>
<AnnotationsProgressIcon isCompleted />
<Text.H4B>Why annotate logs?</Text.H4B>
<Text.H4B>Why annotate traces?</Text.H4B>
</div>
<Text.H5 color='foregroundMuted'>
Annotating logs helps improve accuracy by providing valuable feedback.
Annotating traces helps improve accuracy by providing valuable feedback.
Identifying errors helps us refine algorithms and deliver better
results.
</Text.H5>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

import { usePaywallModal } from '$/app/(private)/providers/PaywallModalProvider'
import { formatCount } from '$/lib/formatCount'
import { formatCount } from '@latitude-data/constants/formatCount'
import { Icon } from '@latitude-data/web-ui/atoms/Icons'
import { Popover } from '@latitude-data/web-ui/atoms/Popover'
import { Text } from '@latitude-data/web-ui/atoms/Text'
Expand Down Expand Up @@ -110,25 +110,12 @@ export function LatteUsageInfo({
<b>{formatCount(usage.billable)}</b>
<span className='mx-0.5'>/</span>
{formatCount(usage.limit)} available credits
{/* TODO(credits): Uncomment this when clients are informed about */}
{/* {!!usage.unbillable &&
` (+${formatCount(usage.unbillable)} bonus)`} */}
</Text.H6>
)}
<UsageBar incurring={incurring} unbillable={usage.unbillable} />
</div>
<Text.H6 color='foregroundMuted' noWrap ellipsis>
Resets {format(usage.resetsAt, 'MMMM d, yyyy')}
{/* TODO(credits): Uncomment this when clients are informed about */}
{/* {incurring > 100 &&
usage.limit !== 'unlimited' &&
!FREE_PLANS.includes(plan) && (
<Text.H6 color='foregroundMuted' noWrap ellipsis>
<br />
You will be billed{' '}
{formatCount(usage.billable - usage.limit)} extra credits
</Text.H6>
)} */}
</Text.H6>
</div>
</Popover.Content>
Expand All @@ -146,12 +133,6 @@ function UsageBar({
const incurring = useMemo(() => {
return Math.max(0, Math.min(100, incurringMaybeOverflowing))
}, [incurringMaybeOverflowing])

// TODO(credits): Uncomment this when clients are informed about
// const bonus = useMemo(() => {
// return Math.max(0, Math.min(100, unbillable * 3))
// }, [unbillable])

return (
<div className='max-w-[300px] flex items-center justify-start gap-1 overflow-hidden rounded-full shrink-0'>
<div className='w-[200px] max-w-[200px] flex items-center justify-start gap-1 overflow-hidden rounded-full'>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { formatCostInMillicents, formatDuration } from '$/app/_lib/formatUtils'
import { usePlaygroundChat } from '$/hooks/playgroundChat/usePlaygroundChat'
import { formatCount } from '$/lib/formatCount'
import { formatCount } from '@latitude-data/constants/formatCount'
import { LegacyVercelSDKVersion4Usage as LanguageModelUsage } from '@latitude-data/constants'
import { Button } from '@latitude-data/web-ui/atoms/Button'
import { Icon } from '@latitude-data/web-ui/atoms/Icons'
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/RunPanelStats/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { formatCostInMillicents, formatDuration } from '$/app/_lib/formatUtils'
import { formatCount } from '$/lib/formatCount'
import { formatCount } from '@latitude-data/constants/formatCount'
import { Badge } from '@latitude-data/web-ui/atoms/Badge'
import { Button } from '@latitude-data/web-ui/atoms/Button'
import { Text } from '@latitude-data/web-ui/atoms/Text'
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/evaluations/human/Rating.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { use, useCallback, useMemo } from 'react'
import { formatCount } from '$/lib/formatCount'
import { formatCount } from '@latitude-data/constants/formatCount'
import {
EvaluationType,
EvaluationV2,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/evaluations/llm/Custom.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { formatCount } from '$/lib/formatCount'
import { formatCount } from '@latitude-data/constants/formatCount'
import {
EvaluationType,
LLM_EVALUATION_CUSTOM_PROMPT_DOCUMENTATION,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/evaluations/llm/Rating.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { formatCount } from '$/lib/formatCount'
import { formatCount } from '@latitude-data/constants/formatCount'
import {
EvaluationType,
LlmEvaluationMetric,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/evaluations/llm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MessageList, MessageListSkeleton } from '$/components/ChatWrapper'
import DebugToggle from '$/components/DebugToggle'
import { MetadataItem } from '$/components/MetadataItem'
import useModelOptions from '$/hooks/useModelOptions'
import { formatCount } from '$/lib/formatCount'
import { formatCount } from '@latitude-data/constants/formatCount'
import useCurrentWorkspace from '$/stores/currentWorkspace'
import useProviders from '$/stores/providerApiKeys'
import { useProviderLog } from '$/stores/providerLogs'
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/evaluations/rule/LengthCount.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { formatCount } from '$/lib/formatCount'
import { formatCount } from '@latitude-data/constants/formatCount'
import {
EvaluationType,
RuleEvaluationLengthCountSpecification,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Text } from '@latitude-data/web-ui/atoms/Text'
import { cn } from '@latitude-data/web-ui/utils'
import { useMemo } from 'react'
import { REWARD_VALUES, RewardType } from '@latitude-data/core/constants'
import { formatCount } from '$/lib/formatCount'
import { formatCount } from '@latitude-data/constants/formatCount'

export function RewardItem({
description,
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/services/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export enum BackofficeRoutes {
grants = 'grants',
promocodes = 'promocodes',
integrations = 'integrations',
weekly = 'weekly',
}

const BACKOFFICE_ROOT = '/backoffice'
Expand Down Expand Up @@ -80,6 +81,11 @@ export const ROUTES = {
[BackofficeRoutes.integrations]: {
root: `${BACKOFFICE_ROOT}/integrations`,
},
[BackofficeRoutes.weekly]: {
root: `${BACKOFFICE_ROOT}/weekly`,
withWorkspaceId: (workspaceId: number) =>
`${BACKOFFICE_ROOT}/weekly?workspaceId=${workspaceId}`,
},
},
noWorkspace: {
root: '/no-workspace',
Expand Down
Loading
Loading