Skip to content

Commit 37a0ed9

Browse files
authored
feat(data-warehouse): add 30 warehouse sources to the self-driving inbox (#3597)
1 parent ee65ff2 commit 37a0ed9

10 files changed

Lines changed: 812 additions & 305 deletions

File tree

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

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,37 @@ export interface SignalSourceConfig {
254254
| "conversations"
255255
| "error_tracking"
256256
| "pganalyze"
257-
| "signals_scout";
257+
| "signals_scout"
258+
| "freshdesk"
259+
| "freshservice"
260+
| "front"
261+
| "gorgias"
262+
| "kustomer"
263+
| "dixa"
264+
| "plain"
265+
| "gitlab"
266+
| "gitea"
267+
| "shortcut"
268+
| "sentry"
269+
| "rollbar"
270+
| "bugsnag"
271+
| "honeybadger"
272+
| "raygun"
273+
| "snyk"
274+
| "sonarqube"
275+
| "semgrep"
276+
| "rapid7_insightvm"
277+
| "featurebase"
278+
| "frill"
279+
| "aha"
280+
| "uservoice"
281+
| "productboard"
282+
| "canny"
283+
| "asknicely"
284+
| "retently"
285+
| "appfigures"
286+
| "appfollow"
287+
| "judgeme_reviews";
258288
source_type:
259289
| "session_analysis_cluster"
260290
| "evaluation"
@@ -263,7 +293,10 @@ export interface SignalSourceConfig {
263293
| "issue_created"
264294
| "issue_reopened"
265295
| "issue_spiking"
266-
| "cross_source_issue";
296+
| "cross_source_issue"
297+
| "scanner_finding"
298+
| "feedback"
299+
| "review";
267300
enabled: boolean;
268301
config: Record<string, unknown>;
269302
created_at: string;

packages/core/src/inbox/signalSourceService.ts

Lines changed: 44 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,20 @@ import type {
44
PostHogAPIClient,
55
SignalSourceConfig,
66
} from "@posthog/api-client/posthog-client";
7+
import {
8+
EXTERNAL_INBOX_SOURCES,
9+
type SignalRecordKind,
10+
sourceNeedsFullRefresh,
11+
type ToggleableSourceProduct,
12+
} from "@posthog/shared";
713
import type { SignalUserAutonomyConfig } from "@posthog/shared/domain-types";
814
import { injectable } from "inversify";
915

10-
export interface SignalSourceValues {
11-
session_replay: boolean;
12-
error_tracking: boolean;
13-
github: boolean;
14-
linear: boolean;
15-
jira: boolean;
16-
zendesk: boolean;
17-
conversations: boolean;
18-
pganalyze: boolean;
19-
}
20-
21-
export type SignalSourceProduct = keyof SignalSourceValues;
22-
23-
export type WarehouseSourceProduct =
24-
| "github"
25-
| "linear"
26-
| "jira"
27-
| "zendesk"
28-
| "pganalyze";
16+
export type SignalSourceProduct = ToggleableSourceProduct;
17+
export type SignalSourceValues = Record<SignalSourceProduct, boolean>;
18+
// Any warehouse source can be a setup target; membership in DATA_WAREHOUSE_SOURCES is the
19+
// runtime narrowing, so this stays a broad alias rather than a hand-kept union.
20+
export type WarehouseSourceProduct = SignalSourceProduct;
2921

3022
export interface SignalSourceState {
3123
requiresSetup: boolean;
@@ -37,20 +29,16 @@ export interface ToggleSourceResult {
3729
isFirstConnection: boolean;
3830
}
3931

40-
type SourceProduct = SignalSourceConfig["source_product"];
4132
type SourceType = SignalSourceConfig["source_type"];
4233

43-
const SOURCE_TYPE_MAP: Record<
44-
Exclude<SourceProduct, "error_tracking" | "llm_analytics" | "signals_scout">,
45-
SourceType
46-
> = {
34+
// Non-warehouse toggles are hard-wired; warehouse sources are derived from the shared
35+
// EXTERNAL_INBOX_SOURCES registry (kept in sync with the UI hook).
36+
const SOURCE_TYPE_MAP: Partial<Record<SignalSourceProduct, SourceType>> = {
4737
session_replay: "session_analysis_cluster",
48-
github: "issue",
49-
linear: "issue",
50-
jira: "issue",
51-
zendesk: "ticket",
5238
conversations: "ticket",
53-
pganalyze: "issue",
39+
...Object.fromEntries(
40+
EXTERNAL_INBOX_SOURCES.map((s) => [s.product, s.recordKind]),
41+
),
5442
};
5543

5644
const ERROR_TRACKING_SOURCE_TYPES: SourceType[] = [
@@ -60,30 +48,27 @@ const ERROR_TRACKING_SOURCE_TYPES: SourceType[] = [
6048
];
6149

6250
const DATA_WAREHOUSE_SOURCES: Record<
63-
WarehouseSourceProduct,
64-
{ dwSourceType: string; requiredTable: string }
65-
> = {
66-
github: { dwSourceType: "Github", requiredTable: "issues" },
67-
linear: { dwSourceType: "Linear", requiredTable: "issues" },
68-
jira: { dwSourceType: "Jira", requiredTable: "issues" },
69-
zendesk: { dwSourceType: "Zendesk", requiredTable: "tickets" },
70-
pganalyze: { dwSourceType: "PgAnalyze", requiredTable: "issues" },
71-
};
51+
string,
52+
{ dwSourceType: string; requiredTable: string; recordKind: SourceType }
53+
> = Object.fromEntries(
54+
EXTERNAL_INBOX_SOURCES.map((s) => [
55+
s.product,
56+
{
57+
dwSourceType: s.dwSourceType,
58+
requiredTable: s.requiredTables[0],
59+
recordKind: s.recordKind,
60+
},
61+
]),
62+
);
7263

7364
const ALL_SOURCE_PRODUCTS: SignalSourceProduct[] = [
7465
"session_replay",
7566
"error_tracking",
76-
"github",
77-
"linear",
78-
"jira",
79-
"zendesk",
8067
"conversations",
81-
"pganalyze",
68+
...EXTERNAL_INBOX_SOURCES.map((s) => s.product as SignalSourceProduct),
8269
];
8370

84-
function isWarehouseSource(
85-
product: SignalSourceProduct,
86-
): product is WarehouseSourceProduct {
71+
function isWarehouseSource(product: SignalSourceProduct): boolean {
8772
return product in DATA_WAREHOUSE_SOURCES;
8873
}
8974

@@ -106,16 +91,9 @@ function findExternalSource(
10691
export function computeSourceValues(
10792
configs: SignalSourceConfig[] | undefined,
10893
): SignalSourceValues {
109-
const result: SignalSourceValues = {
110-
session_replay: false,
111-
error_tracking: false,
112-
github: false,
113-
linear: false,
114-
jira: false,
115-
zendesk: false,
116-
conversations: false,
117-
pganalyze: false,
118-
};
94+
const result = Object.fromEntries(
95+
ALL_SOURCE_PRODUCTS.map((p) => [p, false]),
96+
) as SignalSourceValues;
11997
if (!configs?.length) {
12098
return result;
12199
}
@@ -199,11 +177,9 @@ export class SignalSourceService {
199177
return;
200178
}
201179

202-
const issuesFullReplication =
203-
(product === "github" || product === "linear" || product === "jira") &&
204-
dwConfig.requiredTable === "issues";
205-
206-
if (issuesFullReplication) {
180+
// Issue-like records (issues, findings, feedback, reviews) mutate after creation, so
181+
// they need full-refresh sync; tickets are append-only.
182+
if (sourceNeedsFullRefresh(dwConfig.recordKind as SignalRecordKind)) {
207183
const needsUpdate =
208184
!requiredSchema.should_sync ||
209185
requiredSchema.sync_type !== "full_refresh";
@@ -319,20 +295,15 @@ export class SignalSourceService {
319295
configs: SignalSourceConfig[] | undefined,
320296
): Promise<void> {
321297
const existing = configs?.find((c) => c.source_product === product);
298+
const sourceType = SOURCE_TYPE_MAP[product];
322299
if (existing) {
323300
await client.updateSignalSourceConfig(projectId, existing.id, {
324301
enabled,
325302
});
326-
} else if (enabled) {
303+
} else if (enabled && sourceType) {
327304
await client.createSignalSourceConfig(projectId, {
328305
source_product: product,
329-
source_type:
330-
SOURCE_TYPE_MAP[
331-
product as Exclude<
332-
SourceProduct,
333-
"error_tracking" | "llm_analytics" | "signals_scout"
334-
>
335-
],
306+
source_type: sourceType,
336307
enabled: true,
337308
});
338309
}
@@ -345,13 +316,14 @@ export class SignalSourceService {
345316
configs: SignalSourceConfig[] | undefined,
346317
): Promise<ToggleSourceResult> {
347318
const existing = configs?.find((c) => c.source_product === product);
348-
if (!existing) {
319+
const sourceType = SOURCE_TYPE_MAP[product];
320+
if (!existing && sourceType) {
349321
await client.createSignalSourceConfig(projectId, {
350322
source_product: product,
351-
source_type: SOURCE_TYPE_MAP[product],
323+
source_type: sourceType,
352324
enabled: true,
353325
});
354-
} else if (!existing.enabled) {
326+
} else if (existing && !existing.enabled) {
355327
await client.updateSignalSourceConfig(projectId, existing.id, {
356328
enabled: true,
357329
});

packages/shared/src/analytics-events.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -803,7 +803,37 @@ export interface SignalSourceConnectedProperties {
803803
| "zendesk"
804804
| "conversations"
805805
| "pganalyze"
806-
| "llm_analytics";
806+
| "llm_analytics"
807+
| "freshdesk"
808+
| "freshservice"
809+
| "front"
810+
| "gorgias"
811+
| "kustomer"
812+
| "dixa"
813+
| "plain"
814+
| "gitlab"
815+
| "gitea"
816+
| "shortcut"
817+
| "sentry"
818+
| "rollbar"
819+
| "bugsnag"
820+
| "honeybadger"
821+
| "raygun"
822+
| "snyk"
823+
| "sonarqube"
824+
| "semgrep"
825+
| "rapid7_insightvm"
826+
| "featurebase"
827+
| "frill"
828+
| "aha"
829+
| "uservoice"
830+
| "productboard"
831+
| "canny"
832+
| "asknicely"
833+
| "retently"
834+
| "appfigures"
835+
| "appfollow"
836+
| "judgeme_reviews";
807837
/** True when this is a brand-new createSignalSourceConfig, false for re-enable of an existing config. */
808838
is_first_connection: boolean;
809839
/** True when the connection went through the DataSourceSetup wizard (warehouse OAuth path). */

0 commit comments

Comments
 (0)