Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(data-warehouse): add salesforce integration #23212

Merged
merged 64 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
33c6b20
add boilerplate
EDsCODE Jun 24, 2024
6382aa1
Merge branch 'master' into dw-salesforce-integration
EDsCODE Jun 25, 2024
d612887
working
EDsCODE Jun 25, 2024
f3bc292
incremental
EDsCODE Jun 25, 2024
bd17653
Update query snapshots
github-actions[bot] Jun 25, 2024
71d5473
use verified source
EDsCODE Jun 26, 2024
10e89c4
merge base
EDsCODE Aug 8, 2024
1ac7d80
Merge branch 'dw-salesforce-integration' of github.com:PostHog/postho…
EDsCODE Aug 8, 2024
3d9011c
Update query snapshots
github-actions[bot] Aug 8, 2024
4a30dcf
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 8, 2024
f5bbe03
Update UI snapshots for `chromium` (2)
github-actions[bot] Aug 8, 2024
bfa8c20
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 8, 2024
1df1a51
Merge branch 'master' into dw-salesforce-integration
EDsCODE Aug 9, 2024
c94868a
merge main
EDsCODE Aug 9, 2024
ead1d27
new stub
EDsCODE Aug 9, 2024
23011d2
incremental working
EDsCODE Aug 9, 2024
7f932d0
typing
EDsCODE Aug 12, 2024
5ae3dba
use convert
EDsCODE Aug 12, 2024
59b6ffa
remove dead code
EDsCODE Aug 12, 2024
21292e9
salesforce caption and token refresh
EDsCODE Aug 12, 2024
640dbc0
Merge branch 'master' into dw-salesforce-integration
EDsCODE Aug 12, 2024
c70bf9c
clean up logic
EDsCODE Aug 12, 2024
1314d2a
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 12, 2024
1a01d89
Update UI snapshots for `chromium` (2)
github-actions[bot] Aug 12, 2024
3b57aaf
make it a config
EDsCODE Aug 12, 2024
1e4a8f1
Update UI snapshots for `chromium` (2)
github-actions[bot] Aug 12, 2024
4a8015e
Merge branch 'dw-salesforce-integration' of github.com:PostHog/postho…
EDsCODE Aug 12, 2024
5c2c3b5
adjust migration
EDsCODE Aug 12, 2024
4e94229
typing
EDsCODE Aug 12, 2024
29254bc
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 12, 2024
26bab75
typing
EDsCODE Aug 12, 2024
8fad358
fix timing
EDsCODE Aug 12, 2024
3abe179
Merge branch 'dw-salesforce-integration' of github.com:PostHog/postho…
EDsCODE Aug 12, 2024
760f456
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 12, 2024
8988dd5
Merge branch 'master' into dw-salesforce-integration
EDsCODE Aug 12, 2024
e53e649
typing
EDsCODE Aug 12, 2024
ae5855a
Merge branch 'dw-salesforce-integration' of github.com:PostHog/postho…
EDsCODE Aug 12, 2024
41d5a72
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 12, 2024
8980eca
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 12, 2024
80e48fb
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 12, 2024
ced3b39
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 12, 2024
44bff35
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 12, 2024
9ca598e
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 12, 2024
d55bc54
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 12, 2024
635466b
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 12, 2024
43a20ae
remove dependency upgrades
EDsCODE Aug 12, 2024
740b3ef
Merge branch 'dw-salesforce-integration' of github.com:PostHog/postho…
EDsCODE Aug 12, 2024
15bffca
prune
EDsCODE Aug 13, 2024
9674a53
Merge branch 'master' into dw-salesforce-integration
EDsCODE Aug 13, 2024
115b154
merge main
EDsCODE Aug 13, 2024
daa46d6
Merge branch 'dw-salesforce-integration' of github.com:PostHog/postho…
EDsCODE Aug 13, 2024
d3e0c0f
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 13, 2024
6e89305
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 13, 2024
8a6d5e8
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 13, 2024
97a8128
Merge main
EDsCODE Aug 13, 2024
7d2cd1d
Merge branch 'dw-salesforce-integration' of github.com:PostHog/postho…
EDsCODE Aug 13, 2024
36ea722
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 13, 2024
dd60148
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 13, 2024
9e541c4
Merge branch 'master' into dw-salesforce-integration
EDsCODE Aug 13, 2024
7dd8274
Merge branch 'master' into dw-salesforce-integration
EDsCODE Aug 13, 2024
5528bfd
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 13, 2024
21f2e27
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 13, 2024
69a6e35
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 13, 2024
5c273cc
Update UI snapshots for `chromium` (1)
github-actions[bot] Aug 13, 2024
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
25 changes: 25 additions & 0 deletions frontend/public/salesforce-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 21 additions & 6 deletions frontend/src/scenes/data-warehouse/external/forms/SourceForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { LemonInput, LemonSelect, LemonSwitch, LemonTextArea } from '@posthog/lemon-ui'
import { useValues } from 'kea'
import { Form, Group } from 'kea-forms'
import { LemonField } from 'lib/lemon-ui/LemonField'

Expand All @@ -8,6 +9,8 @@ import { SOURCE_DETAILS, sourceWizardLogic } from '../../new/sourceWizardLogic'

interface SourceFormProps {
sourceConfig: SourceConfig
showPrefix?: boolean
showSourceFields?: boolean
}

const sourceFieldToElement = (field: SourceFieldConfig): JSX.Element => {
Expand Down Expand Up @@ -73,14 +76,26 @@ const sourceFieldToElement = (field: SourceFieldConfig): JSX.Element => {
}

export default function SourceForm({ sourceConfig }: SourceFormProps): JSX.Element {
const { source } = useValues(sourceWizardLogic)
const showSourceFields = SOURCE_DETAILS[sourceConfig.name].showSourceForm
? SOURCE_DETAILS[sourceConfig.name].showSourceForm?.(source.payload)
: true
const showPrefix = SOURCE_DETAILS[sourceConfig.name].showPrefix
? SOURCE_DETAILS[sourceConfig.name].showPrefix?.(source.payload)
: true

return (
<Form logic={sourceWizardLogic} formKey="sourceConnectionDetails" className="space-y-4" enableFormOnSubmit>
<Group name="payload">
{SOURCE_DETAILS[sourceConfig.name].fields.map((field) => sourceFieldToElement(field))}
</Group>
<LemonField name="prefix" label="Table Prefix (optional)">
<LemonInput className="ph-ignore-input" data-attr="prefix" placeholder="internal_" />
</LemonField>
{showSourceFields && (
<Group name="payload">
{SOURCE_DETAILS[sourceConfig.name].fields.map((field) => sourceFieldToElement(field))}
</Group>
)}
{showPrefix && (
<LemonField name="prefix" label="Table Prefix (optional)">
<LemonInput className="ph-ignore-input" data-attr="prefix" placeholder="internal_" />
</LemonField>
)}
</Form>
)
}
115 changes: 103 additions & 12 deletions frontend/src/scenes/data-warehouse/new/sourceWizardLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const Caption = (): JSX.Element => (
)

export const getHubspotRedirectUri = (): string => `${window.location.origin}/data-warehouse/hubspot/redirect`
export const getSalesforceRedirectUri = (): string => `${window.location.origin}/data-warehouse/salesforce/redirect`

export const SOURCE_DETAILS: Record<ExternalDataSourceType, SourceConfig> = {
Stripe: {
Expand All @@ -67,6 +68,7 @@ export const SOURCE_DETAILS: Record<ExternalDataSourceType, SourceConfig> = {
name: 'Hubspot',
fields: [],
caption: 'Succesfully authenticated with Hubspot. Please continue here to complete the source setup',
oauthPayload: ['code'],
},
Postgres: {
name: 'Postgres',
Expand Down Expand Up @@ -422,6 +424,22 @@ export const SOURCE_DETAILS: Record<ExternalDataSourceType, SourceConfig> = {
},
],
},
Salesforce: {
name: 'Salesforce',
fields: [
{
name: 'subdomain',
label: 'Salesforce subdomain',
type: 'text',
required: true,
placeholder: '',
},
],
caption: 'Succesfully authenticated with Salesforce. Please continue here to complete the source setup',
showPrefix: (payload) => !!payload.code,
showSourceForm: (payload) => !payload.code,
oauthPayload: ['code', 'subdomain'],
},
}

export const buildKeaFormDefaultFromSourceDetails = (
Expand Down Expand Up @@ -748,6 +766,27 @@ export const sourceWizardLogic = kea<sourceWizardLogicType>([
}
},
],
addToSalesforceButtonUrl: [
(s) => [s.preflight],
(preflight) => {
return (subdomain: string) => {
const clientId = preflight?.data_warehouse_integrations?.salesforce.client_id

if (!clientId) {
return null
}

const params = new URLSearchParams()
params.set('client_id', clientId)
params.set('redirect_uri', `${window.location.origin}/data-warehouse/salesforce/redirect`)
params.set('response_type', 'code')
params.set('scope', 'refresh_token api')
params.set('state', subdomain)

return `https://${subdomain}.my.salesforce.com/services/oauth2/authorize?${params.toString()}`
}
},
],
Comment on lines +769 to +789
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice to have: to use Bens new oauth stuff he added - although there is certainly a larger piece of work around how we do credentials, so may not be one for now. e.g. storing S3 details, or Stripe connection details, etc.

modalTitle: [
(s) => [s.currentStep],
(currentStep) => {
Expand Down Expand Up @@ -885,6 +924,17 @@ export const sourceWizardLogic = kea<sourceWizardLogicType>([
})
return
}
case 'salesforce': {
actions.updateSource({
source_type: 'Salesforce',
payload: {
code: searchParams.code,
subdomain: searchParams.subdomain,
redirect_uri: getSalesforceRedirectUri(),
},
})
break
}
default:
lemonToast.error(`Something went wrong.`)
}
Expand Down Expand Up @@ -925,6 +975,13 @@ export const sourceWizardLogic = kea<sourceWizardLogicType>([
if (kind === 'hubspot') {
router.actions.push(urls.dataWarehouseTable(), { kind, code: searchParams.code })
}
if (kind === 'salesforce') {
router.actions.push(urls.dataWarehouseTable(), {
kind,
code: searchParams.code,
subdomain: searchParams.state,
})
}
},
'/data-warehouse/new': (_, searchParams) => {
if (searchParams.kind == 'hubspot' && searchParams.code) {
Expand All @@ -934,16 +991,41 @@ export const sourceWizardLogic = kea<sourceWizardLogicType>([
})
actions.setStep(2)
}
if (searchParams.kind == 'salesforce' && searchParams.code) {
actions.selectConnector(SOURCE_DETAILS['Salesforce'])
actions.handleRedirect(searchParams.kind, {
code: searchParams.code,
subdomain: searchParams.subdomain,
})
actions.setStep(2)
}
},
})),
forms(({ actions, values }) => ({
sourceConnectionDetails: {
defaults: buildKeaFormDefaultFromSourceDetails(SOURCE_DETAILS),
errors: (sourceValues) => {
if (
values.selectedConnector &&
SOURCE_DETAILS[values.selectedConnector?.name].oauthPayload &&
SOURCE_DETAILS[values.selectedConnector.name].oauthPayload?.every(
(element) => values.source.payload[element]
)
) {
return {}
}
return getErrorsForFields(values.selectedConnector?.fields ?? [], sourceValues as any)
},
submit: async (sourceValues) => {
if (values.selectedConnector) {
if (
values.selectedConnector.name === 'Salesforce' &&
(!values.source.payload.code || !values.source.payload.subdomain)
) {
window.open(values.addToSalesforceButtonUrl(sourceValues.payload.subdomain) as string)
return
}

const payload = {
...sourceValues,
source_type: values.selectedConnector.name,
Expand All @@ -953,19 +1035,28 @@ export const sourceWizardLogic = kea<sourceWizardLogicType>([
try {
await api.externalDataSources.source_prefix(payload.source_type, sourceValues.prefix)

const payloadKeys = (values.selectedConnector?.fields ?? []).map((n) => n.name)
// salesforce doesn't need to store the payload. The relevant fielsd will already be handled from the URL
// only update the prefix
if (values.selectedConnector.name === 'Salesforce') {
actions.updateSource({
...values.source,
prefix: sourceValues.prefix,
})
} else {
const payloadKeys = (values.selectedConnector?.fields ?? []).map((n) => n.name)

// Only store the keys of the source type we're using
actions.updateSource({
...payload,
payload: {
source_type: values.selectedConnector.name,
...payloadKeys.reduce((acc, cur) => {
acc[cur] = payload['payload'][cur]
return acc
}, {} as Record<string, any>),
},
})
// Only store the keys of the source type we're using
actions.updateSource({
...payload,
payload: {
source_type: values.selectedConnector.name,
...payloadKeys.reduce((acc, cur) => {
acc[cur] = payload['payload'][cur]
return acc
}, {} as Record<string, any>),
},
})
}

actions.setIsLoading(false)
} catch (e: any) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import IconGoogleCloudStorage from 'public/services/google-cloud-storage.png'
import IconHubspot from 'public/services/hubspot.png'
import IconMySQL from 'public/services/mysql.png'
import IconPostgres from 'public/services/postgres.png'
import IconSalesforce from 'public/services/salesforce.png'
import IconSnowflake from 'public/services/snowflake.png'
import IconStripe from 'public/services/stripe.png'
import IconZendesk from 'public/services/zendesk.png'
Expand Down Expand Up @@ -183,6 +184,7 @@ export function RenderDataWarehouseSourceIcon({
'google-cloud': IconGoogleCloudStorage,
'cloudflare-r2': IconCloudflare,
azure: Iconazure,
Salesforce: IconSalesforce,
}[type]

return (
Expand Down
16 changes: 15 additions & 1 deletion frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2966,6 +2966,9 @@ export interface PreflightStatus {
hubspot: {
client_id?: string
}
salesforce: {
client_id?: string
}
}
/** Whether PostHog is running in DEBUG mode. */
is_debug?: boolean
Expand Down Expand Up @@ -3857,7 +3860,15 @@ export enum DataWarehouseSettingsTab {
SelfManaged = 'self-managed',
}

export const externalDataSources = ['Stripe', 'Hubspot', 'Postgres', 'MySQL', 'Zendesk', 'Snowflake'] as const
export const externalDataSources = [
'Stripe',
'Hubspot',
'Postgres',
'MySQL',
'Zendesk',
'Snowflake',
'Salesforce',
] as const

export type ExternalDataSourceType = (typeof externalDataSources)[number]

Expand Down Expand Up @@ -4234,6 +4245,9 @@ export interface SourceConfig {
caption: string | React.ReactNode
fields: SourceFieldConfig[]
disabledReason?: string | null
showPrefix?: (payload: Record<string, any>) => boolean
showSourceForm?: (payload: Record<string, any>) => boolean
oauthPayload?: string[]
}

export interface ProductPricingTierSubrows {
Expand Down
Loading
Loading