Skip to content

Commit fa31cc1

Browse files
authored
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
1 parent 3d788e3 commit fa31cc1

5 files changed

Lines changed: 172 additions & 0 deletions

File tree

packages/api-client/src/posthog-client.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import type {
5555
ChannelFeedMessage,
5656
ChannelFeedMessageEvent,
5757
CodeReferenceArtefact,
58+
CodeUserNotificationSettings,
5859
CommitArtefact,
5960
CommitDiffResponse,
6061
DismissalArtefact,
@@ -4112,6 +4113,47 @@ export class PostHogAPIClient {
41124113
return (await response.json()) as SignalUserAutonomyConfig;
41134114
}
41144115

4116+
async getCodeUserNotificationSettings(): Promise<CodeUserNotificationSettings> {
4117+
const url = new URL(`${this.api.baseUrl}/api/code/user_settings/`);
4118+
const path = "/api/code/user_settings/";
4119+
4120+
const response = await this.api.fetcher.fetch({
4121+
method: "get",
4122+
url,
4123+
path,
4124+
});
4125+
4126+
if (!response.ok) {
4127+
throw new Error(
4128+
`Failed to fetch Code notification settings: ${response.statusText}`,
4129+
);
4130+
}
4131+
return (await response.json()) as CodeUserNotificationSettings;
4132+
}
4133+
4134+
async updateCodeUserNotificationSettings(updates: {
4135+
slack_mention_notifications: boolean;
4136+
}): Promise<CodeUserNotificationSettings> {
4137+
const url = new URL(`${this.api.baseUrl}/api/code/user_settings/`);
4138+
const path = "/api/code/user_settings/";
4139+
4140+
const response = await this.api.fetcher.fetch({
4141+
method: "post",
4142+
url,
4143+
path,
4144+
overrides: {
4145+
body: JSON.stringify(updates),
4146+
},
4147+
});
4148+
4149+
if (!response.ok) {
4150+
throw new Error(
4151+
`Failed to update Code notification settings: ${response.statusText}`,
4152+
);
4153+
}
4154+
return (await response.json()) as CodeUserNotificationSettings;
4155+
}
4156+
41154157
async getSlackChannelsForIntegration(
41164158
integrationId: number,
41174159
params?: SlackChannelsQueryParams,

packages/shared/src/domain-types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,12 @@ export interface SignalUserAutonomyConfig {
796796
updated_at?: string;
797797
}
798798

799+
/** Per-user PostHog Code notification preferences, served by `/api/code/user_settings/`. */
800+
export interface CodeUserNotificationSettings {
801+
/** Send a Slack DM when someone @-mentions the user in a channel thread. Opt-in. */
802+
slack_mention_notifications: boolean;
803+
}
804+
799805
export interface SlackChannelOption {
800806
id: string;
801807
name: string;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { ANALYTICS_EVENTS, PROJECT_BLUEBIRD_FLAG } from "@posthog/shared";
2+
import { useFeatureFlag } from "@posthog/ui/features/feature-flags/useFeatureFlag";
3+
import { useIntegrationSelectors } from "@posthog/ui/features/integrations/store";
4+
import {
5+
useCodeUserNotificationSettings,
6+
useCodeUserNotificationSettingsMutations,
7+
} from "@posthog/ui/features/settings/sections/useCodeUserNotificationSettings";
8+
import { track } from "@posthog/ui/shell/analytics";
9+
import { Flex, Switch, Text } from "@radix-ui/themes";
10+
11+
/**
12+
* Opt-in Slack DMs for channel-thread @-mentions. The preference lives on the
13+
* PostHog backend (per user, across projects); delivery uses the team's Slack
14+
* integration, so the section only shows once a workspace is connected.
15+
*/
16+
export function SlackMentionNotificationsSettings() {
17+
// Channel threads (and their @-mentions) only exist behind the bluebird flag.
18+
const channelsEnabled = useFeatureFlag(
19+
PROJECT_BLUEBIRD_FLAG,
20+
import.meta.env.DEV,
21+
);
22+
const { hasSlackIntegration } = useIntegrationSelectors();
23+
const { data: settings, isLoading } = useCodeUserNotificationSettings();
24+
const { handleUpdateSlackMentionNotifications } =
25+
useCodeUserNotificationSettingsMutations();
26+
27+
// The connect-workspace prompt is the parent section's job.
28+
if (!channelsEnabled || !hasSlackIntegration) return null;
29+
30+
const enabled = settings?.slack_mention_notifications ?? false;
31+
32+
const onCheckedChange = (checked: boolean) => {
33+
track(ANALYTICS_EVENTS.SETTING_CHANGED, {
34+
setting_name: "slack_mention_notifications",
35+
new_value: checked,
36+
old_value: enabled,
37+
});
38+
void handleUpdateSlackMentionNotifications(checked);
39+
};
40+
41+
return (
42+
<Flex
43+
direction="column"
44+
gap="2"
45+
className="border-(--gray-5) border-t border-dashed pt-4"
46+
>
47+
<Flex align="center" justify="between" gap="3">
48+
<Flex direction="column" gap="1">
49+
<Text className="font-medium text-(--gray-12) text-sm">
50+
Mention notifications
51+
</Text>
52+
<Text className="text-(--gray-11) text-[13px]">
53+
Get a Slack DM when someone @-mentions you in a channel thread.
54+
</Text>
55+
</Flex>
56+
<Switch
57+
checked={enabled}
58+
disabled={isLoading}
59+
onCheckedChange={onCheckedChange}
60+
aria-label="Slack DM on @-mention"
61+
/>
62+
</Flex>
63+
</Flex>
64+
);
65+
}

packages/ui/src/features/settings/sections/SlackSettings.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { openUrlInBrowser } from "@posthog/ui/utils/browser";
55
import { getPostHogUrl } from "@posthog/ui/utils/urls";
66
import { Button, Flex, Text, Tooltip } from "@radix-ui/themes";
77
import { SlackInboxNotificationsSettings } from "./SlackInboxNotificationsSettings";
8+
import { SlackMentionNotificationsSettings } from "./SlackMentionNotificationsSettings";
89

910
export function SlackSettings() {
1011
const projectId = useAuthStateValue((s) => s.currentProjectId);
@@ -52,6 +53,8 @@ export function SlackSettings() {
5253
isLoading={isLoading}
5354
showHeader={false}
5455
/>
56+
57+
<SlackMentionNotificationsSettings />
5558
</Flex>
5659
);
5760
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { CodeUserNotificationSettings } from "@posthog/shared/domain-types";
2+
import { useOptionalAuthenticatedClient } from "@posthog/ui/features/auth/authClient";
3+
import { useAuthenticatedQuery } from "@posthog/ui/hooks/useAuthenticatedQuery";
4+
import { toast } from "@posthog/ui/primitives/toast";
5+
import { useQueryClient } from "@tanstack/react-query";
6+
import { useCallback } from "react";
7+
8+
const CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY = [
9+
"code-user-notification-settings",
10+
] as const;
11+
12+
/** The user's PostHog Code notification settings (all-defaults until first saved). */
13+
export function useCodeUserNotificationSettings() {
14+
return useAuthenticatedQuery(CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY, (client) =>
15+
client.getCodeUserNotificationSettings(),
16+
);
17+
}
18+
19+
/** Mutations for the settings above; reads come from `useCodeUserNotificationSettings`. */
20+
export function useCodeUserNotificationSettingsMutations() {
21+
const client = useOptionalAuthenticatedClient();
22+
const queryClient = useQueryClient();
23+
24+
const handleUpdateSlackMentionNotifications = useCallback(
25+
async (enabled: boolean) => {
26+
if (!client) return;
27+
const previous = queryClient.getQueryData<CodeUserNotificationSettings>(
28+
CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY,
29+
);
30+
// Optimistic: the switch reflects the new state immediately, reverted on failure.
31+
queryClient.setQueryData<CodeUserNotificationSettings>(
32+
CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY,
33+
{ slack_mention_notifications: enabled },
34+
);
35+
try {
36+
const fresh = await client.updateCodeUserNotificationSettings({
37+
slack_mention_notifications: enabled,
38+
});
39+
queryClient.setQueryData(CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY, fresh);
40+
} catch (error: unknown) {
41+
queryClient.setQueryData(
42+
CODE_USER_NOTIFICATION_SETTINGS_QUERY_KEY,
43+
previous,
44+
);
45+
const message =
46+
error instanceof Error
47+
? error.message
48+
: "Failed to update mention notifications";
49+
toast.error(message);
50+
}
51+
},
52+
[client, queryClient],
53+
);
54+
55+
return { handleUpdateSlackMentionNotifications };
56+
}

0 commit comments

Comments
 (0)