Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions packages/api-client/src/posthog-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ export interface SignalSourceConfig {
| "llm_analytics"
| "github"
| "linear"
| "jira"
| "zendesk"
| "conversations"
| "error_tracking"
Expand Down Expand Up @@ -402,6 +403,86 @@ export interface ExternalDataSource {
schemas?: ExternalDataSourceSchema[] | string;
}

/**
* Field-config variants for an external data source's connect form, as served
* by the `external_data_sources/wizard/` endpoint. Mirrors PostHog Cloud's
* `SourceFieldConfig` union (`posthog/schema.py`). The backend is the single
* source of truth for which credential fields a source needs, so forms can be
* rendered generically instead of hardcoded per source.
*/
export interface SourceFieldInputConfig {
type:
| "text"
| "email"
| "search"
| "url"
| "password"
| "time"
| "number"
| "textarea";
name: string;
label: string;
required: boolean;
placeholder?: string;
caption?: string | null;
/** Redacted from API responses; render as a password field. */
secret?: boolean;
}

export interface SourceFieldOauthConfig {
type: "oauth";
name: string;
label: string;
kind: string;
required: boolean;
requiredScopes?: string;
}

export interface SourceFieldSelectConfigOption {
label: string;
value: string;
fields?: SourceFieldConfig[];
}

export interface SourceFieldSelectConfig {
type: "select";
name: string;
label: string;
required: boolean;
defaultValue?: string;
options: SourceFieldSelectConfigOption[];
}

export interface SourceFieldSwitchGroupConfig {
type: "switch-group";
name: string;
label: string;
caption?: string;
default?: boolean;
fields: SourceFieldConfig[];
}

/** Field types the generic renderer does not (yet) handle inline. */
export interface SourceFieldUnsupportedConfig {
type: "ssh-tunnel" | "file-upload";
name: string;
label: string;
}

export type SourceFieldConfig =
| SourceFieldInputConfig
| SourceFieldOauthConfig
| SourceFieldSelectConfig
| SourceFieldSwitchGroupConfig
| SourceFieldUnsupportedConfig;

export interface SourceConfig {
name: string;
label?: string;
caption?: string;
fields: SourceFieldConfig[];
}

export interface FolderInstructionsUser {
id?: number;
uuid?: string;
Expand Down Expand Up @@ -2108,6 +2189,30 @@ export class PostHogAPIClient {
return response.data as unknown as ExternalDataSource;
}

/**
* Fetch the connect-form field schema for external data source types from the
* warehouse wizard endpoint. Pass `sourceType` (e.g. `"Jira"`) to scope to one
* source; omit to fetch every source's config. Returns a map keyed by the
* capitalized source type string.
*/
async getExternalDataSourceConfigs(
projectId: number,
sourceType?: string,
): Promise<Record<string, SourceConfig>> {
const url = new URL(
`${this.api.baseUrl}/api/environments/${projectId}/external_data_sources/wizard/`,
);
if (sourceType) {
url.searchParams.set("source_type", sourceType);
}
const path = `/api/environments/${projectId}/external_data_sources/wizard/`;
const response = await this.api.fetcher.fetch({ method: "get", url, path });
if (!response.ok) {
throw new Error(`Failed to fetch source configs: ${response.statusText}`);
}
return (await response.json()) as Record<string, SourceConfig>;
}

async updateExternalDataSchema(
projectId: number,
schemaId: string,
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/inbox/dataSourceService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ import type { PostHogAPIClient } from "@posthog/api-client/posthog-client";
import { inject, injectable } from "inversify";
import { LINEAR_OAUTH_FLOW, type LinearOAuthFlow } from "./identifiers";

export type DataSourceType = "github" | "linear" | "zendesk" | "pganalyze";
export type DataSourceType =
| "github"
| "linear"
| "jira"
| "zendesk"
| "pganalyze";

const REQUIRED_SCHEMAS: Record<DataSourceType, string[]> = {
github: ["issues"],
linear: ["issues"],
jira: ["issues"],
zendesk: ["tickets"],
pganalyze: ["issues", "servers"],
};
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/inbox/signalSourceService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface SignalSourceValues {
error_tracking: boolean;
github: boolean;
linear: boolean;
jira: boolean;
zendesk: boolean;
conversations: boolean;
pganalyze: boolean;
Expand All @@ -22,6 +23,7 @@ export type SignalSourceProduct = keyof SignalSourceValues;
export type WarehouseSourceProduct =
| "github"
| "linear"
| "jira"
| "zendesk"
| "pganalyze";

Expand All @@ -45,6 +47,7 @@ const SOURCE_TYPE_MAP: Record<
session_replay: "session_analysis_cluster",
github: "issue",
linear: "issue",
jira: "issue",
zendesk: "ticket",
conversations: "ticket",
pganalyze: "issue",
Expand All @@ -62,6 +65,7 @@ const DATA_WAREHOUSE_SOURCES: Record<
> = {
github: { dwSourceType: "Github", requiredTable: "issues" },
linear: { dwSourceType: "Linear", requiredTable: "issues" },
jira: { dwSourceType: "Jira", requiredTable: "issues" },
zendesk: { dwSourceType: "Zendesk", requiredTable: "tickets" },
pganalyze: { dwSourceType: "PgAnalyze", requiredTable: "issues" },
};
Expand All @@ -71,6 +75,7 @@ const ALL_SOURCE_PRODUCTS: SignalSourceProduct[] = [
"error_tracking",
"github",
"linear",
"jira",
"zendesk",
"conversations",
"pganalyze",
Expand Down Expand Up @@ -106,6 +111,7 @@ export function computeSourceValues(
error_tracking: false,
github: false,
linear: false,
jira: false,
zendesk: false,
conversations: false,
pganalyze: false,
Expand Down Expand Up @@ -194,7 +200,7 @@ export class SignalSourceService {
}

const issuesFullReplication =
(product === "github" || product === "linear") &&
(product === "github" || product === "linear" || product === "jira") &&
dwConfig.requiredTable === "issues";

if (issuesFullReplication) {
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/analytics-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,7 @@ export interface SignalSourceConnectedProperties {
| "signals_scout"
| "github"
| "linear"
| "jira"
| "zendesk"
| "conversations"
| "pganalyze"
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/inbox-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type SourceProduct =
| "llm_analytics"
| "github"
| "linear"
| "jira"
| "zendesk"
| "conversations"
| "pganalyze"
Expand Down
14 changes: 13 additions & 1 deletion packages/ui/src/features/inbox/components/DataSourceSetup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Button } from "@posthog/quill";
import { useAuthenticatedClient } from "@posthog/ui/features/auth/authClient";
import { useAuthStateValue } from "@posthog/ui/features/auth/store";
import { GitHubRepoPicker } from "@posthog/ui/features/folder-picker/GitHubRepoPicker";
import { DynamicSourceSetup } from "@posthog/ui/features/inbox/components/DynamicSourceSetup";
import {
describeGithubConnectError,
useGithubConnect,
Expand All @@ -16,11 +17,12 @@ import { Box, Flex, Text, TextField } from "@radix-ui/themes";
import { useMutation } from "@tanstack/react-query";
import { useCallback, useEffect, useRef, useState } from "react";

type DataSourceType = "github" | "linear" | "zendesk" | "pganalyze";
type DataSourceType = "github" | "linear" | "jira" | "zendesk" | "pganalyze";

const REQUIRED_SCHEMAS: Record<DataSourceType, string[]> = {
github: ["issues"],
linear: ["issues"],
jira: ["issues"],
zendesk: ["tickets"],
pganalyze: ["issues", "servers"],
};
Expand Down Expand Up @@ -52,6 +54,16 @@ export function DataSourceSetup({
return <GitHubSetup onComplete={onComplete} onCancel={onCancel} />;
case "linear":
return <LinearSetup onComplete={onComplete} onCancel={onCancel} />;
case "jira":
return (
<DynamicSourceSetup
sourceType="Jira"
title="Connect Jira"
schemas={schemasPayload("jira")}
onComplete={onComplete}
onCancel={onCancel}
/>
);
case "zendesk":
return <ZendeskSetup onComplete={onComplete} onCancel={onCancel} />;
case "pganalyze":
Expand Down
Loading
Loading