Skip to content

Commit 7db73a9

Browse files
fix: dlq status and dashboard dropdown menu
1 parent 790121a commit 7db73a9

7 files changed

Lines changed: 42 additions & 27 deletions

File tree

.dev.vars.example

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ DATABASE_URL="postgresql://user:password@localhost:5432/codra_dev"
3838
# MUST be a separate database to avoid data loss during test sweeps.
3939
TEST_DATABASE_URL="postgresql://user:password@localhost:5432/codra_test"
4040

41-
# --- Cloudflare Management (Optional) ---
42-
# Required only for inspecting/replaying Dead Letter Queues (DLQ) via /api/dlq
41+
# --- Cloudflare DLQ / Queue Management (Required) ---
42+
# Required for DLQ inspection, replay, and purge via /api/dlq
43+
# Create or identify the DLQ queue, then set CF_DLQ_ID to that queue's ID.
4344
# Generate token at https://dash.cloudflare.com/profile/api-tokens (Queues:Edit permission)
4445
CF_API_TOKEN="REPLACE_WITH_CLOUDFLARE_API_TOKEN"
4546
CF_ACCOUNT_ID="REPLACE_WITH_YOUR_CLOUDFLARE_ACCOUNT_ID"

README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,13 @@ What the deploy button does not provision for you:
5555
- GitHub App credentials
5656
- GitHub OAuth app credentials
5757
- Gemini API key
58+
- Cloudflare API credentials for DLQ inspection, replay, and purge
5859

5960
That means the deploy flow is best thought of as "Cloudflare infrastructure bootstrap", followed by a short secrets setup step.
6061

6162
For this repo's own production deployment, the checked-in route and binding IDs in [`wrangler.jsonc`](/wrangler.jsonc) are intentional. They are what keep `codra.run` deploying against the same Worker, KV namespace, and queues. If you fork Codra, replace those values with your own resources.
6263

63-
## Required Secrets And Local DB Vars
64+
## Required Secrets, DLQ, And Local DB Vars
6465

6566
Codra expects these secrets in Cloudflare production and in local `.dev.vars` for development:
6667

@@ -70,23 +71,30 @@ Codra expects these secrets in Cloudflare production and in local `.dev.vars` fo
7071
- `GITHUB_CLIENT_ID`
7172
- `GITHUB_CLIENT_SECRET`
7273
- `GEMINI_API_KEY`
74+
- `CF_API_TOKEN`
75+
- `CF_ACCOUNT_ID`
76+
77+
DLQ setup is required during installation because Codra includes `/api/dlq` inspection, replay, and purge workflows. Create or identify the dead letter queue and copy its queue ID into `CF_DLQ_ID`:
78+
79+
```bash
80+
npx wrangler queues create codra-review-dlq
81+
npx wrangler queues list
82+
```
83+
84+
The Cloudflare API token must have Queues edit access for the account that owns the Worker queues. `CF_DLQ_ID` is a required environment variable, not a secret, and should point at the `codra-review-dlq` queue used by the `dead_letter_queue` consumer config in [`wrangler.jsonc`](/wrangler.jsonc).
7385

7486
Local development and migrations also need:
7587

7688
- `CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE` for local Worker DB access
7789
- `DATABASE_URL` for local/admin migrations
7890

79-
Optional, only for DLQ inspection and replay APIs:
80-
81-
- `CF_API_TOKEN`
82-
- `CF_ACCOUNT_ID`
83-
8491
The expected local shape is already documented in [`.dev.vars.example`](/.dev.vars.example).
8592

8693
In the checked-in production Wrangler config, these values are regular environment vars rather than secrets:
8794

8895
- `AUTH_CALLBACK_URL`
8996
- `DASHBOARD_ALLOWED_USERS`
97+
- `CF_DLQ_ID`
9098

9199
## Dashboard Auth
92100

src/client/components/features/stats/time-range-select.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function TimeRangeSelect({ value, onValueChange, className }: TimeRangeSe
2727
value: range.value.toString(),
2828
}))}
2929
leadingIcon={<Clock className="h-3.5 w-3.5" />}
30-
triggerClassName={cn('w-[160px]', className)}
30+
triggerClassName={cn('w-44', className)}
3131
/>
3232
);
3333
}

src/client/components/ui/select.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,26 +54,26 @@ export function Select({
5454
triggerClassName
5555
)}
5656
>
57-
<span className="flex min-w-0 items-center gap-2">
57+
<span className="flex min-w-0 flex-1 items-center gap-2">
5858
{leadingIcon && (
5959
<span className="shrink-0 text-primary/70">
6060
{leadingIcon}
6161
</span>
6262
)}
63-
<span className="truncate">
63+
<span className="min-w-0 truncate">
6464
{selectedOption ? selectedOption.label : placeholder}
6565
</span>
6666
</span>
6767
<ChevronDown className="h-4 w-4 opacity-50 shrink-0 ml-2" />
6868
</Button>
6969
</DropdownMenuTrigger>
70-
<DropdownMenuContent className="min-w-[var(--radix-dropdown-menu-trigger-width)]">
70+
<DropdownMenuContent className="w-max min-w-[var(--radix-dropdown-menu-trigger-width)]">
7171
{options.map((option) => (
7272
<DropdownMenuItem
7373
key={option.value}
7474
onClick={() => onValueChange(option.value)}
7575
className={cn(
76-
'cursor-pointer',
76+
'cursor-pointer whitespace-nowrap',
7777
value === option.value && 'bg-accent font-medium dark:bg-primary/[0.12]'
7878
)}
7979
>

src/server/env.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ export interface AppBindings {
4343
GEMINI_API_KEY: string;
4444
BOT_USERNAME: string;
4545
ENVIRONMENT: string;
46-
CF_API_TOKEN?: string;
47-
CF_ACCOUNT_ID?: string;
48-
CF_DLQ_ID?: string;
46+
CF_API_TOKEN: string;
47+
CF_ACCOUNT_ID: string;
48+
CF_DLQ_ID: string;
4949
}
5050

5151
export interface AppVariables {

src/server/routes/api/dlq.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { logger } from '@server/core/logger';
1919
*
2020
* Note: Cloudflare's pull-queue API requires a CF_API_TOKEN with
2121
* Queues:Edit permissions. Add CF_API_TOKEN and CF_ACCOUNT_ID as
22-
* Worker secrets (see .dev.vars.example).
22+
* Worker secrets, and CF_DLQ_ID as a required var (see .dev.vars.example).
2323
*/
2424

2525
const CF_QUEUES_BASE = 'https://api.cloudflare.com/client/v4';
@@ -74,15 +74,16 @@ export function createDlqRouter() {
7474

7575
const apiToken = c.env.CF_API_TOKEN;
7676
const accountId = c.env.CF_ACCOUNT_ID;
77+
const dlqId = c.env.CF_DLQ_ID;
7778

78-
if (!apiToken || !accountId) {
79-
return jsonError('CF_API_TOKEN and CF_ACCOUNT_ID secrets are required for DLQ inspection.', 503);
79+
if (!apiToken || !accountId || !dlqId) {
80+
return jsonError('CF_API_TOKEN, CF_ACCOUNT_ID, and CF_DLQ_ID are required DLQ installation configuration for inspection.', 503);
8081
}
8182

8283
try {
8384
const result = await cfQueuesRequest(
8485
'POST',
85-
`/accounts/${accountId}/queues/${c.env.CF_DLQ_ID}/messages/pull`,
86+
`/accounts/${accountId}/queues/${dlqId}/messages/pull`,
8687
apiToken,
8788
{ batch_size: batchSize, visibility_timeout_ms: 30_000 },
8889
) as { messages?: CfQueueMessage[] };
@@ -119,9 +120,10 @@ export function createDlqRouter() {
119120

120121
const apiToken = c.env.CF_API_TOKEN;
121122
const accountId = c.env.CF_ACCOUNT_ID;
123+
const dlqId = c.env.CF_DLQ_ID;
122124

123-
if (!apiToken || !accountId) {
124-
return jsonError('CF_API_TOKEN and CF_ACCOUNT_ID secrets are required for DLQ replay.', 503);
125+
if (!apiToken || !accountId || !dlqId) {
126+
return jsonError('CF_API_TOKEN, CF_ACCOUNT_ID, and CF_DLQ_ID are required DLQ installation configuration for replay.', 503);
125127
}
126128

127129
// Step 1 – pull the specific messages so we have their bodies.
@@ -131,7 +133,7 @@ export function createDlqRouter() {
131133
try {
132134
const result = await cfQueuesRequest(
133135
'POST',
134-
`/accounts/${accountId}/queues/${c.env.CF_DLQ_ID}/messages/pull`,
136+
`/accounts/${accountId}/queues/${dlqId}/messages/pull`,
135137
apiToken,
136138
{ batch_size: 100, visibility_timeout_ms: 60_000 },
137139
) as { messages?: CfQueueMessage[] };
@@ -153,7 +155,7 @@ export function createDlqRouter() {
153155
try {
154156
await cfQueuesRequest(
155157
'POST',
156-
`/accounts/${accountId}/queues/${c.env.CF_DLQ_ID}/messages/ack`,
158+
`/accounts/${accountId}/queues/${dlqId}/messages/ack`,
157159
apiToken,
158160
{ acks: targets.map((m) => ({ lease_id: m.lease_id })) },
159161
);
@@ -203,15 +205,16 @@ export function createDlqRouter() {
203205

204206
const apiToken = c.env.CF_API_TOKEN;
205207
const accountId = c.env.CF_ACCOUNT_ID;
208+
const dlqId = c.env.CF_DLQ_ID;
206209

207-
if (!apiToken || !accountId) {
208-
return jsonError('CF_API_TOKEN and CF_ACCOUNT_ID secrets are required for DLQ purge.', 503);
210+
if (!apiToken || !accountId || !dlqId) {
211+
return jsonError('CF_API_TOKEN, CF_ACCOUNT_ID, and CF_DLQ_ID are required DLQ installation configuration for purge.', 503);
209212
}
210213

211214
try {
212215
await cfQueuesRequest(
213216
'POST',
214-
`/accounts/${accountId}/queues/${c.env.CF_DLQ_ID}/messages/ack`,
217+
`/accounts/${accountId}/queues/${dlqId}/messages/ack`,
215218
apiToken,
216219
{ acks: body.data.lease_ids.map((id) => ({ lease_id: id })) },
217220
);

test/helpers.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ export function createTestEnv(overrides: Partial<AppBindings> = {}): AppBindings
108108
GEMINI_API_KEY: 'gemini-key',
109109
BOT_USERNAME: 'codra-app',
110110
ENVIRONMENT: 'test',
111+
CF_API_TOKEN: 'cf-api-token',
112+
CF_ACCOUNT_ID: 'cf-account-id',
113+
CF_DLQ_ID: 'cf-dlq-id',
111114
...overrides,
112115
};
113116
}

0 commit comments

Comments
 (0)