When a company has "require board approval for new agents" enabled, hiring an agent creates a pending approval that requires the user (as a board member) to approve before the agent can start working. However, the sidebar inbox badge did not include pending approvals in its count, so there was no visual indicator that action was needed. Users had no way of knowing an approval was waiting unless they happened to open the Inbox page manually. The root cause: the sidebar-badges service correctly included approvals in the inbox total, but the route handler overwrites badges.inbox to add alertsCount and staleIssueCount — and in doing so dropped badges.approvals from the sum. Add badges.approvals to the inbox count recalculation so that pending and revision-requested approvals surface in the sidebar notification badge alongside failed runs, alerts, stale work, and join requests. Affected files: - server/src/routes/sidebar-badges.ts
55 lines
2.2 KiB
TypeScript
55 lines
2.2 KiB
TypeScript
import { Router } from "express";
|
|
import type { Db } from "@paperclipai/db";
|
|
import { and, eq, sql } from "drizzle-orm";
|
|
import { joinRequests } from "@paperclipai/db";
|
|
import { sidebarBadgeService } from "../services/sidebar-badges.js";
|
|
import { issueService } from "../services/issues.js";
|
|
import { accessService } from "../services/access.js";
|
|
import { dashboardService } from "../services/dashboard.js";
|
|
import { assertCompanyAccess } from "./authz.js";
|
|
|
|
export function sidebarBadgeRoutes(db: Db) {
|
|
const router = Router();
|
|
const svc = sidebarBadgeService(db);
|
|
const issueSvc = issueService(db);
|
|
const access = accessService(db);
|
|
const dashboard = dashboardService(db);
|
|
|
|
router.get("/companies/:companyId/sidebar-badges", async (req, res) => {
|
|
const companyId = req.params.companyId as string;
|
|
assertCompanyAccess(req, companyId);
|
|
let canApproveJoins = false;
|
|
if (req.actor.type === "board") {
|
|
canApproveJoins =
|
|
req.actor.source === "local_implicit" ||
|
|
Boolean(req.actor.isInstanceAdmin) ||
|
|
(await access.canUser(companyId, req.actor.userId, "joins:approve"));
|
|
} else if (req.actor.type === "agent" && req.actor.agentId) {
|
|
canApproveJoins = await access.hasPermission(companyId, "agent", req.actor.agentId, "joins:approve");
|
|
}
|
|
|
|
const joinRequestCount = canApproveJoins
|
|
? await db
|
|
.select({ count: sql<number>`count(*)` })
|
|
.from(joinRequests)
|
|
.where(and(eq(joinRequests.companyId, companyId), eq(joinRequests.status, "pending_approval")))
|
|
.then((rows) => Number(rows[0]?.count ?? 0))
|
|
: 0;
|
|
|
|
const badges = await svc.get(companyId, {
|
|
joinRequests: joinRequestCount,
|
|
});
|
|
const summary = await dashboard.summary(companyId);
|
|
const staleIssueCount = await issueSvc.staleCount(companyId, 24 * 60);
|
|
const hasFailedRuns = badges.failedRuns > 0;
|
|
const alertsCount =
|
|
(summary.agents.error > 0 && !hasFailedRuns ? 1 : 0) +
|
|
(summary.costs.monthBudgetCents > 0 && summary.costs.monthUtilizationPercent >= 80 ? 1 : 0);
|
|
badges.inbox = badges.failedRuns + alertsCount + staleIssueCount + joinRequestCount + badges.approvals;
|
|
|
|
res.json(badges);
|
|
});
|
|
|
|
return router;
|
|
}
|