* Notify reviewers that content has entered pending_review. * * Posts to both (a) the working group's Slack channel if one is configured * (keeps WG-specific review flows working) and (b) the system-wide editorial * review channel if one is configured (central queue for admins and * committee l
(
workingGroupId: string,
perspective: {
id: string;
title: string;
slug: string;
excerpt: string | null;
content_type: string;
content: string | null;
proposed_at: string;
},
authorName: string,
proposerUserId: string
)
| 148 | * that's fine, we just skip what's missing. |
| 149 | */ |
| 150 | async function notifyPendingReview( |
| 151 | workingGroupId: string, |
| 152 | perspective: { |
| 153 | id: string; |
| 154 | title: string; |
| 155 | slug: string; |
| 156 | excerpt: string | null; |
| 157 | content_type: string; |
| 158 | content: string | null; |
| 159 | proposed_at: string; |
| 160 | }, |
| 161 | authorName: string, |
| 162 | proposerUserId: string |
| 163 | ): Promise<void> { |
| 164 | const pool = getPool(); |
| 165 | |
| 166 | // Fetch the working group, committee leads, and channel config in one |
| 167 | // round-trip so we can enrich the message with lead names. The leads |
| 168 | // query only surfaces WorkOS-linked users — slack-only leads (leaders |
| 169 | // added by Slack ID before mapping) won't appear in the `*Leads:*` line. |
| 170 | // That matches the current data-integrity requirement; a reviewer seeing |
| 171 | // a missing leads line just means the committee has unmapped leads. |
| 172 | const [wgResult, leadersResult, editorialChannel] = await Promise.all([ |
| 173 | pool.query( |
| 174 | `SELECT name, slack_channel_id FROM working_groups WHERE id = $1`, |
| 175 | [workingGroupId] |
| 176 | ), |
| 177 | pool.query( |
| 178 | `SELECT u.first_name, u.last_name, u.email |
| 179 | FROM working_group_leaders wgl |
| 180 | LEFT JOIN slack_user_mappings sm ON wgl.user_id = sm.slack_user_id AND sm.workos_user_id IS NOT NULL |
| 181 | LEFT JOIN users u ON u.workos_user_id = COALESCE(sm.workos_user_id, wgl.user_id) |
| 182 | WHERE wgl.working_group_id = $1 |
| 183 | AND u.workos_user_id IS NOT NULL |
| 184 | LIMIT 10`, |
| 185 | [workingGroupId] |
| 186 | ), |
| 187 | getEditorialChannel(), |
| 188 | ]); |
| 189 | |
| 190 | if (wgResult.rows.length === 0) { |
| 191 | logger.warn({ workingGroupId }, 'Working group not found for pending-review notification'); |
| 192 | return; |
| 193 | } |
| 194 | |
| 195 | const { name: wgName, slack_channel_id: wgChannelId } = wgResult.rows[0]; |
| 196 | const editorialChannelId = editorialChannel.channel_id; |
| 197 | |
| 198 | if (!wgChannelId && !editorialChannelId) { |
| 199 | logger.debug({ workingGroupId }, 'No Slack channels configured for pending-review notification'); |
| 200 | return; |
| 201 | } |
| 202 | |
| 203 | const leadNames: string[] = leadersResult.rows |
| 204 | .map(r => (r.first_name && r.last_name ? `${r.first_name} ${r.last_name}` : r.email?.split('@')[0] || null)) |
| 205 | .filter((n): n is string => !!n); |
| 206 | |
| 207 | const safeTitle = escapeSlackText(perspective.title, 180); |
no test coverage detected