From fa31cc1445f2a7f8426e50af44218ca2dcf858be Mon Sep 17 00:00:00 2001 From: Adam Bowker Date: Fri, 17 Jul 2026 16:33:53 -0400 Subject: [PATCH 1/2] Add Slack DM toggle for channel thread @-mentions Opt-in setting (default off) in the Slack settings section, gated behind the project-bluebird flag. Reads/writes the new /api/code/user_settings/ endpoint; delivery itself is backend-driven via the team's Slack integration (PostHog/posthog#72087). Generated-By: PostHog Code Task-Id: 69e0939c-e6e1-4efa-9d5e-771b88f03b5a --- packages/api-client/src/posthog-client.ts | 42 ++++++++++++ packages/shared/src/domain-types.ts | 6 ++ .../SlackMentionNotificationsSettings.tsx | 65 +++++++++++++++++++ .../settings/sections/SlackSettings.tsx | 3 + .../useCodeUserNotificationSettings.ts | 56 ++++++++++++++++ 5 files changed, 172 insertions(+) create mode 100644 packages/ui/src/features/settings/sections/SlackMentionNotificationsSettings.tsx create mode 100644 packages/ui/src/features/settings/sections/useCodeUserNotificationSettings.ts diff --git a/packages/api-client/src/posthog-client.ts b/packages/api-client/src/posthog-client.ts index a9c5e72318..e282dc915d 100644 --- a/packages/api-client/src/posthog-client.ts +++ b/packages/api-client/src/posthog-client.ts @@ -55,6 +55,7 @@ import type { ChannelFeedMessage, ChannelFeedMessageEvent, CodeReferenceArtefact, + CodeUserNotificationSettings, CommitArtefact, CommitDiffResponse, DismissalArtefact, @@ -4112,6 +4113,47 @@ export class PostHogAPIClient { return (await response.json()) as SignalUserAutonomyConfig; } + async getCodeUserNotificationSettings(): Promise { + const url = new URL(`${this.api.baseUrl}/api/code/user_settings/`); + const path = "/api/code/user_settings/"; + + const response = await this.api.fetcher.fetch({ + method: "get", + url, + path, + }); + + if (!response.ok) { + throw new Error( + `Failed to fetch Code notification settings: ${response.statusText}`, + ); + } + return (await response.json()) as CodeUserNotificationSettings; + } + + async updateCodeUserNotificationSettings(updates: { + slack_mention_notifications: boolean; + }): Promise { + const url = new URL(`${this.api.baseUrl}/api/code/user_settings/`); + const path = "/api/code/user_settings/"; + + const response = await this.api.fetcher.fetch({ + method: "post", + url, + path, + overrides: { + body: JSON.stringify(updates), + }, + }); + + if (!response.ok) { + throw new Error( + `Failed to update Code notification settings: ${response.statusText}`, + ); + } + return (await response.json()) as CodeUserNotificationSettings; + } + async getSlackChannelsForIntegration( integrationId: number, params?: SlackChannelsQueryParams, diff --git a/packages/shared/src/domain-types.ts b/packages/shared/src/domain-types.ts index 98f9d90ff9..77d69dc07a 100644 --- a/packages/shared/src/domain-types.ts +++ b/packages/shared/src/domain-types.ts @@ -796,6 +796,12 @@ export interface SignalUserAutonomyConfig { updated_at?: string; } +/** Per-user PostHog Code notification preferences, served by `/api/code/user_settings/`. */ +export interface CodeUserNotificationSettings { + /** Send a Slack DM when someone @-mentions the user in a channel thread. Opt-in. */ + slack_mention_notifications: boolean; +} + export interface SlackChannelOption { id: string; name: string; diff --git a/packages/ui/src/features/settings/sections/SlackMentionNotificationsSettings.tsx b/packages/ui/src/features/settings/sections/SlackMentionNotificationsSettings.tsx new file mode 100644 index 0000000000..280dfa293f --- /dev/null +++ b/packages/ui/src/features/settings/sections/SlackMentionNotificationsSettings.tsx @@ -0,0 +1,65 @@ +import { ANALYTICS_EVENTS, PROJECT_BLUEBIRD_FLAG } from "@posthog/shared"; +import { useFeatureFlag } from "@posthog/ui/features/feature-flags/useFeatureFlag"; +import { useIntegrationSelectors } from "@posthog/ui/features/integrations/store"; +import { + useCodeUserNotificationSettings, + useCodeUserNotificationSettingsMutations, +} from "@posthog/ui/features/settings/sections/useCodeUserNotificationSettings"; +import { track } from "@posthog/ui/shell/analytics"; +import { Flex, Switch, Text } from "@radix-ui/themes"; + +/** + * Opt-in Slack DMs for channel-thread @-mentions. The preference lives on the + * PostHog backend (per user, across projects); delivery uses the team's Slack + * integration, so the section only shows once a workspace is connected. + */ +export function SlackMentionNotificationsSettings() { + // Channel threads (and their @-mentions) only exist behind the bluebird flag. + const channelsEnabled = useFeatureFlag( + PROJECT_BLUEBIRD_FLAG, + import.meta.env.DEV, + ); + const { hasSlackIntegration } = useIntegrationSelectors(); + const { data: settings, isLoading } = useCodeUserNotificationSettings(); + const { handleUpdateSlackMentionNotifications } = + useCodeUserNotificationSettingsMutations(); + + // The connect-workspace prompt is the parent section's job. + if (!channelsEnabled || !hasSlackIntegration) return null; + + const enabled = settings?.slack_mention_notifications ?? false; + + const onCheckedChange = (checked: boolean) => { + track(ANALYTICS_EVENTS.SETTING_CHANGED, { + setting_name: "slack_mention_notifications", + new_value: checked, + old_value: enabled, + }); + void handleUpdateSlackMentionNotifications(checked); + }; + + return ( + + + + + Mention notifications + + + Get a Slack DM when someone @-mentions you in a channel thread. + + + + + + ); +} diff --git a/packages/ui/src/features/settings/sections/SlackSettings.tsx b/packages/ui/src/features/settings/sections/SlackSettings.tsx index f93e66845d..dc17c79255 100644 --- a/packages/ui/src/features/settings/sections/SlackSettings.tsx +++ b/packages/ui/src/features/settings/sections/SlackSettings.tsx @@ -5,6 +5,7 @@ import { openUrlInBrowser } from "@posthog/ui/utils/browser"; import { getPostHogUrl } from "@posthog/ui/utils/urls"; import { Button, Flex, Text, Tooltip } from "@radix-ui/themes"; import { SlackInboxNotificationsSettings } from "./SlackInboxNotificationsSettings"; +import { SlackMentionNotificationsSettings } from "./SlackMentionNotificationsSettings"; export function SlackSettings() { const projectId = useAuthStateValue((s) => s.currentProjectId); @@ -52,6 +53,8 @@ export function SlackSettings() { isLoading={isLoading} showHeader={false} /> + + ); } diff --git a/packages/ui/src/features/settings/sections/useCodeUserNotificationSettings.ts b/packages/ui/src/features/settings/sections/useCodeUserNotificationSettings.ts new file mode 100644 index 0000000000..3a8e062ffa --- /dev/null +++ b/packages/ui/src/features/settings/sections/useCodeUserNotificationSettings.ts @@ -0,0 +1,56 @@ +import type { CodeUserNotificationSettings } from "@posthog/shared/domain-types"; +import { useOptionalAuthenticatedClient } from "@posthog/ui/features/auth/authClient"; +import { useAuthenticatedQuery } from "@posthog/ui/hooks/useAuthenticatedQuery"; +import { toast } from "@posthog/ui/primitives/toast"; +import { useQueryClient } from "@tanstack/react-query"; +import { useCallback } from "react"; + +const CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY = [ + "code-user-notification-settings", +] as const; + +/** The user's PostHog Code notification settings (all-defaults until first saved). */ +export function useCodeUserNotificationSettings() { + return useAuthenticatedQuery(CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY, (client) => + client.getCodeUserNotificationSettings(), + ); +} + +/** Mutations for the settings above; reads come from `useCodeUserNotificationSettings`. */ +export function useCodeUserNotificationSettingsMutations() { + const client = useOptionalAuthenticatedClient(); + const queryClient = useQueryClient(); + + const handleUpdateSlackMentionNotifications = useCallback( + async (enabled: boolean) => { + if (!client) return; + const previous = queryClient.getQueryData( + CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY, + ); + // Optimistic: the switch reflects the new state immediately, reverted on failure. + queryClient.setQueryData( + CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY, + { slack_mention_notifications: enabled }, + ); + try { + const fresh = await client.updateCodeUserNotificationSettings({ + slack_mention_notifications: enabled, + }); + queryClient.setQueryData(CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY, fresh); + } catch (error: unknown) { + queryClient.setQueryData( + CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY, + previous, + ); + const message = + error instanceof Error + ? error.message + : "Failed to update mention notifications"; + toast.error(message); + } + }, + [client, queryClient], + ); + + return { handleUpdateSlackMentionNotifications }; +} From 029c2b5c335ad63eb65d441994206c913e3b4b16 Mon Sep 17 00:00:00 2001 From: Adam Bowker Date: Fri, 17 Jul 2026 16:51:53 -0400 Subject: [PATCH 2/2] Fix Biome formatting in useCodeUserNotificationSettings Generated-By: PostHog Code Task-Id: 69e0939c-e6e1-4efa-9d5e-771b88f03b5a --- .../sections/useCodeUserNotificationSettings.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/features/settings/sections/useCodeUserNotificationSettings.ts b/packages/ui/src/features/settings/sections/useCodeUserNotificationSettings.ts index 3a8e062ffa..a15fd9e0bc 100644 --- a/packages/ui/src/features/settings/sections/useCodeUserNotificationSettings.ts +++ b/packages/ui/src/features/settings/sections/useCodeUserNotificationSettings.ts @@ -11,8 +11,9 @@ const CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY = [ /** The user's PostHog Code notification settings (all-defaults until first saved). */ export function useCodeUserNotificationSettings() { - return useAuthenticatedQuery(CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY, (client) => - client.getCodeUserNotificationSettings(), + return useAuthenticatedQuery( + CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY, + (client) => client.getCodeUserNotificationSettings(), ); } @@ -36,7 +37,10 @@ export function useCodeUserNotificationSettingsMutations() { const fresh = await client.updateCodeUserNotificationSettings({ slack_mention_notifications: enabled, }); - queryClient.setQueryData(CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY, fresh); + queryClient.setQueryData( + CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY, + fresh, + ); } catch (error: unknown) { queryClient.setQueryData( CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY,