Skip to content

Commit b79ae3f

Browse files
committed
Implement weekly email schedule job
We want to send an email with relevant data for the organizations using Latitude. We'll do the processing of this data on monday 1:00 A.M and it will be sent to be ready on users' inbox on monday morning
1 parent f33a192 commit b79ae3f

File tree

78 files changed

+2775
-293
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+2775
-293
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use server'
2+
3+
import { withAdmin } from '../../procedures'
4+
import { z } from 'zod'
5+
import { queues } from '@latitude-data/core/queues'
6+
7+
export const enqueueWeeklyEmailAction = withAdmin
8+
.inputSchema(
9+
z.object({
10+
workspaceId: z.number(),
11+
emails: z.string().optional(),
12+
}),
13+
)
14+
.action(async ({ parsedInput }) => {
15+
const { workspaceId, emails } = parsedInput
16+
17+
// Parse comma-separated emails if provided
18+
const emailList = emails
19+
? emails
20+
.split(',')
21+
.map((e) => e.trim())
22+
.filter((e) => e.length > 0)
23+
: undefined
24+
25+
const { maintenanceQueue } = await queues()
26+
await maintenanceQueue.add(
27+
'sendWeeklyEmailJob',
28+
{
29+
workspaceId,
30+
emails: emailList,
31+
},
32+
{
33+
jobId: `weekly-email-manual-${workspaceId}-${Date.now()}`,
34+
},
35+
)
36+
37+
return { success: true }
38+
})

apps/web/src/app/(admin)/backoffice/_components/BackofficeTabs/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ export function BackofficeTabs({ children }: { children: ReactNode }) {
6161
value: BackofficeRoutes.integrations,
6262
route: ROUTES.backoffice.integrations.root,
6363
},
64+
{
65+
label: 'Weekly',
66+
value: BackofficeRoutes.weekly,
67+
route: ROUTES.backoffice.weekly.root,
68+
},
6469
]}
6570
selected={selected}
6671
/>

apps/web/src/app/(admin)/backoffice/search/workspace/[id]/_components/ToggleBigAccountButton/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { useToast } from '@latitude-data/web-ui/atoms/Toast'
44
import useLatitudeAction from '$/hooks/useLatitudeAction'
55
import { toggleBigAccountAction } from '$/actions/admin/workspaces/toggleBigAccount'
66

7-
export function ToggleBigAccountButton({ workspaceId, isBigAccount }: {
7+
export function ToggleBigAccountButton({
8+
workspaceId,
9+
isBigAccount,
10+
}: {
811
workspaceId: number
912
isBigAccount: boolean
1013
}) {

apps/web/src/app/(admin)/backoffice/search/workspace/[id]/_components/WorkspaceDashboard/index.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,15 @@ export function WorkspaceDashboard({ workspace }: Props) {
9292
issuesUnlocked={workspace.issuesUnlocked}
9393
/>
9494
<div className='flex gap-2'>
95+
<Link
96+
href={ROUTES.backoffice[BackofficeRoutes.weekly].withWorkspaceId(
97+
workspace.id,
98+
)}
99+
>
100+
<Button fancy variant='outline'>
101+
<Text.H5B noWrap>See Weekly</Text.H5B>
102+
</Button>
103+
</Link>
95104
<ChangePlanButton
96105
workspaceId={workspace.id}
97106
currentPlan={workspace.subscription.plan}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
'use client'
2+
3+
import { FormEvent, useCallback, useEffect, useState } from 'react'
4+
import { useSearchParams } from 'next/navigation'
5+
import { Button } from '@latitude-data/web-ui/atoms/Button'
6+
import { Input } from '@latitude-data/web-ui/atoms/Input'
7+
import { TextArea } from '@latitude-data/web-ui/atoms/TextArea'
8+
import { Text } from '@latitude-data/web-ui/atoms/Text'
9+
import { useToast } from '@latitude-data/web-ui/atoms/Toast'
10+
import { enqueueWeeklyEmailAction } from '$/actions/admin/weeklyEmail/enqueueWeeklyEmail'
11+
import useLatitudeAction from '$/hooks/useLatitudeAction'
12+
13+
export function WeeklyEmailForm() {
14+
const searchParams = useSearchParams()
15+
const { toast } = useToast()
16+
const [workspaceId, setWorkspaceId] = useState('')
17+
const [emails, setEmails] = useState('')
18+
19+
// Pre-fill workspaceId from query params if present
20+
useEffect(() => {
21+
const workspaceIdParam = searchParams.get('workspaceId')
22+
if (workspaceIdParam) {
23+
setWorkspaceId(workspaceIdParam)
24+
}
25+
}, [searchParams])
26+
27+
const { execute: enqueueEmail, isPending } = useLatitudeAction(
28+
enqueueWeeklyEmailAction,
29+
{
30+
onSuccess: () => {
31+
toast({
32+
title: 'Success',
33+
description: 'Weekly email job has been enqueued successfully',
34+
})
35+
// Clear form
36+
setWorkspaceId('')
37+
setEmails('')
38+
},
39+
onError: (error) => {
40+
toast({
41+
title: 'Error',
42+
description: error.message,
43+
variant: 'destructive',
44+
})
45+
},
46+
},
47+
)
48+
49+
const handleSubmit = useCallback(
50+
(e: FormEvent) => {
51+
e.preventDefault()
52+
53+
const parsedWorkspaceId = parseInt(workspaceId)
54+
if (isNaN(parsedWorkspaceId)) {
55+
toast({
56+
title: 'Invalid Workspace ID',
57+
description: 'Please enter a valid numeric workspace ID',
58+
variant: 'destructive',
59+
})
60+
return
61+
}
62+
63+
enqueueEmail({
64+
workspaceId: parsedWorkspaceId,
65+
emails: emails.trim() || undefined,
66+
})
67+
},
68+
[workspaceId, emails, enqueueEmail, toast],
69+
)
70+
71+
return (
72+
<form onSubmit={handleSubmit} className='flex flex-col gap-y-4 max-w-2xl'>
73+
<div className='flex flex-col gap-y-2'>
74+
<Text.H5 weight='medium'>Workspace ID</Text.H5>
75+
<Input
76+
type='number'
77+
placeholder='Enter workspace ID'
78+
value={workspaceId}
79+
onChange={(e) => setWorkspaceId(e.target.value)}
80+
required
81+
/>
82+
<Text.H6 color='foregroundMuted'>
83+
The ID of the workspace to send the weekly email for
84+
</Text.H6>
85+
</div>
86+
87+
<div className='flex flex-col gap-y-2'>
88+
<Text.H5 weight='medium'>Email Addresses (Optional)</Text.H5>
89+
<TextArea
90+
placeholder='email1@example.com, email2@example.com'
91+
value={emails}
92+
onChange={(e) => setEmails(e.target.value)}
93+
rows={4}
94+
/>
95+
<Text.H6 color='foregroundMuted'>
96+
Comma-separated list of email addresses. If left empty, the email will
97+
be sent to all workspace members who have opted in to receive weekly
98+
emails.
99+
</Text.H6>
100+
</div>
101+
102+
<div className='flex gap-2'>
103+
<Button type='submit' fancy disabled={isPending}>
104+
{isPending ? 'Enqueueing...' : 'Enqueue Weekly Email'}
105+
</Button>
106+
</div>
107+
</form>
108+
)
109+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use client'
2+
3+
import { Text } from '@latitude-data/web-ui/atoms/Text'
4+
import { WeeklyEmailForm } from './_components/WeeklyEmailForm'
5+
6+
export default function AdminWeeklyEmail() {
7+
return (
8+
<div className='container flex flex-col gap-y-8'>
9+
<section className='flex flex-col gap-y-4'>
10+
<Text.H1>Weekly Email</Text.H1>
11+
<Text.H4 color='foregroundMuted'>
12+
Manually trigger weekly email reports for specific workspaces. You can
13+
optionally provide a comma-separated list of email addresses to send
14+
the report to instead of the workspace members.
15+
</Text.H4>
16+
<WeeklyEmailForm />
17+
</section>
18+
</div>
19+
)
20+
}

apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/ProjectSection/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22

33
import { useMemo } from 'react'
4-
import { formatCount } from '$/lib/formatCount'
4+
import { formatCount } from '@latitude-data/constants/formatCount'
55
import { ROUTES } from '$/services/routes'
66

77
import { RunSourceGroup } from '@latitude-data/constants'

apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/(withTabs)/evaluations/[evaluationUuid]/_components/charts/DailyOverview.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { formatCostInMillicents } from '$/app/_lib/formatUtils'
22
import { useCurrentEvaluationV2 } from '$/app/providers/EvaluationV2Provider'
33
import { EVALUATION_SPECIFICATIONS } from '$/components/evaluations'
4-
import { formatCount } from '$/lib/formatCount'
4+
import { formatCount } from '@latitude-data/constants/formatCount'
55
import { ChartBlankSlate } from '@latitude-data/web-ui/atoms/ChartBlankSlate'
66
import { Text } from '@latitude-data/web-ui/atoms/Text'
77
import { AreaChart, ChartWrapper } from '@latitude-data/web-ui/molecules/Charts'

apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/(withTabs)/evaluations/[evaluationUuid]/_components/charts/VersionOverview.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { formatCostInMillicents } from '$/app/_lib/formatUtils'
22
import { useCurrentEvaluationV2 } from '$/app/providers/EvaluationV2Provider'
33
import { EVALUATION_SPECIFICATIONS } from '$/components/evaluations'
4-
import { formatCount } from '$/lib/formatCount'
4+
import { formatCount } from '@latitude-data/constants/formatCount'
55
import { Badge } from '@latitude-data/web-ui/atoms/Badge'
66
import { ChartBlankSlate } from '@latitude-data/web-ui/atoms/ChartBlankSlate'
77
import { Text } from '@latitude-data/web-ui/atoms/Text'

apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/(withTabs)/experiments/_components/ExperimentsTable/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { useCurrentProject } from '$/app/providers/ProjectProvider'
1818
import { DurationCell } from './DurationCell'
1919
import { ScoreCell } from './ScoreCell'
2020
import { cn } from '@latitude-data/web-ui/utils'
21-
import { formatCount } from '$/lib/formatCount'
21+
import { formatCount } from '@latitude-data/constants/formatCount'
2222
import { useSearchParams } from 'next/navigation'
2323
import { LinkableTablePaginationFooter } from '$/components/TablePaginationFooter'
2424
import { DocumentRoutes, ROUTES } from '$/services/routes'

0 commit comments

Comments
 (0)