Skip to content

Commit 97ac555

Browse files
STRATCONN-6127 - [Pendo Audiences] - post partner meeting updates (#3646)
* post partner meeting updates * unit tests * tidy up * fixing tests * adding region support
1 parent 2b61812 commit 97ac555

13 files changed

Lines changed: 231 additions & 202 deletions

File tree

packages/destination-actions/src/destinations/pendo-audiences/__tests__/index.test.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import nock from 'nock'
22
import { createTestIntegration } from '@segment/actions-core'
33
import Destination from '../index'
4-
import { CONSTANTS } from '../constants'
4+
import { REGIONS, SEGMENT_ENDPOINT } from '../constants'
55

66
const testDestination = createTestIntegration(Destination)
77

88
const settings = {
9-
integrationKey: 'test-integration-key'
9+
integrationKey: 'test-integration-key',
10+
region: REGIONS.DEFAULT.name
1011
}
1112

1213
describe('Pendo Audiences - createAudience', () => {
@@ -15,8 +16,8 @@ describe('Pendo Audiences - createAudience', () => {
1516
})
1617

1718
it('should create a segment and return the segmentId', async () => {
18-
nock(CONSTANTS.API_BASE_URL)
19-
.post(`${CONSTANTS.SEGMENT_ENDPOINT}/upload`, { name: 'My Audience', visitors: [] })
19+
nock(REGIONS.DEFAULT.domain)
20+
.post(`/${SEGMENT_ENDPOINT}/upload`, { name: 'My Audience', visitors: ['empty-visitor'] })
2021
.reply(200, { segmentId: 'seg-abc123' })
2122

2223
const result = await testDestination.createAudience({
@@ -29,8 +30,8 @@ describe('Pendo Audiences - createAudience', () => {
2930
})
3031

3132
it('should use audienceSettings.audienceName over audienceName when provided', async () => {
32-
nock(CONSTANTS.API_BASE_URL)
33-
.post(`${CONSTANTS.SEGMENT_ENDPOINT}/upload`, { name: 'Custom Name', visitors: [] })
33+
nock(REGIONS.DEFAULT.domain)
34+
.post(`/${SEGMENT_ENDPOINT}/upload`, { name: 'Custom Name', visitors: ['empty-visitor'] })
3435
.reply(200, { segmentId: 'seg-custom' })
3536

3637
const result = await testDestination.createAudience({
@@ -61,8 +62,8 @@ describe('Pendo Audiences - getAudience', () => {
6162
it('should return the segmentId when the segment exists', async () => {
6263
const segmentId = 'seg-abc123'
6364

64-
nock(CONSTANTS.API_BASE_URL)
65-
.get(`${CONSTANTS.SEGMENT_ENDPOINT}/${segmentId}`)
65+
nock(REGIONS.DEFAULT.domain)
66+
.get(`/${SEGMENT_ENDPOINT}/${segmentId}`)
6667
.reply(200, { id: segmentId, name: 'My Audience' })
6768

6869
const result = await testDestination.getAudience({
@@ -76,8 +77,8 @@ describe('Pendo Audiences - getAudience', () => {
7677
it('should throw IntegrationError when segment ID in response does not match', async () => {
7778
const segmentId = 'seg-abc123'
7879

79-
nock(CONSTANTS.API_BASE_URL)
80-
.get(`${CONSTANTS.SEGMENT_ENDPOINT}/${segmentId}`)
80+
nock(REGIONS.DEFAULT.domain)
81+
.get(`/${SEGMENT_ENDPOINT}/${segmentId}`)
8182
.reply(200, { id: 'seg-different', name: 'Other Audience' })
8283

8384
await expect(
@@ -91,8 +92,8 @@ describe('Pendo Audiences - getAudience', () => {
9192
it('should throw when Pendo returns 404', async () => {
9293
const segmentId = 'seg-missing'
9394

94-
nock(CONSTANTS.API_BASE_URL)
95-
.get(`${CONSTANTS.SEGMENT_ENDPOINT}/${segmentId}`)
95+
nock(REGIONS.DEFAULT.domain)
96+
.get(`/${SEGMENT_ENDPOINT}/${segmentId}`)
9697
.reply(404, { message: 'Not Found' })
9798

9899
await expect(
Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,24 @@
1-
export const CONSTANTS = {
2-
API_BASE_URL: 'https://app.pendo.io',
3-
SEGMENT_ENDPOINT: '/api/v1/segment'
4-
}
1+
export const SEGMENT_ENDPOINT = 'api/v1/segment'
2+
3+
export const REGIONS = {
4+
DEFAULT: {
5+
name: 'US',
6+
domain: 'https://app.pendo.io'
7+
},
8+
US1: {
9+
name: 'US1',
10+
domain: 'https://us1.app.pendo.io'
11+
},
12+
EUROPE: {
13+
name: 'EU',
14+
domain: 'https://app.eu.pendo.io'
15+
},
16+
JAPAN: {
17+
name: 'JP',
18+
domain: 'https://app.jpn.pendo.io'
19+
},
20+
AUSTRALIA: {
21+
name: 'AU',
22+
domain: 'https://app.au.pendo.io'
23+
}
24+
}
Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
import { RequestClient, IntegrationError } from '@segment/actions-core'
2-
import { CONSTANTS } from './constants'
1+
import { RequestClient, IntegrationError, ErrorCodes } from '@segment/actions-core'
2+
import { PendoDomain } from './types'
33
import type { CreateSegmentJSON, CreateSegmentResponse, GetSegmentResponse } from './types'
4+
import { REGIONS, SEGMENT_ENDPOINT } from './constants'
45

5-
export async function createSegment(request: RequestClient, name: string): Promise<string> {
6-
const json: CreateSegmentJSON = { name, visitors: [] }
6+
export async function createSegment(request: RequestClient, region: string, name: string): Promise<string> {
7+
const json: CreateSegmentJSON = {
8+
name,
9+
visitors: ["empty-visitor"] // Pendo requires at least one visitor to create a segment, but it can be an empty placeholder since we'll be updating the segment with the actual visitors in the syncAudience function
10+
}
711
const response = await request<CreateSegmentResponse>(
8-
`${CONSTANTS.API_BASE_URL}${CONSTANTS.SEGMENT_ENDPOINT}/upload`,
12+
`${getDomain(region)}/${SEGMENT_ENDPOINT}/upload`,
913
{
1014
method: 'POST',
1115
json
@@ -15,9 +19,9 @@ export async function createSegment(request: RequestClient, name: string): Promi
1519
return response.data.segmentId
1620
}
1721

18-
export async function getSegment(request: RequestClient, segmentId: string): Promise<string> {
22+
export async function getSegment(request: RequestClient, region: string, segmentId: string): Promise<string> {
1923
const response = await request<GetSegmentResponse>(
20-
`${CONSTANTS.API_BASE_URL}${CONSTANTS.SEGMENT_ENDPOINT}/${segmentId}`,
24+
`${getDomain(region)}/${SEGMENT_ENDPOINT}/${segmentId}`,
2125
{
2226
method: 'GET'
2327
}
@@ -29,3 +33,13 @@ export async function getSegment(request: RequestClient, segmentId: string): Pro
2933

3034
return segmentId
3135
}
36+
37+
export function getDomain(regionName: string): PendoDomain {
38+
const region = Object.values(REGIONS).find(r => r.name === regionName)
39+
40+
if (!region) {
41+
throw new IntegrationError(`Invalid region: ${regionName}`, ErrorCodes.NOT_IMPLEMENTED, 400);
42+
}
43+
44+
return region.domain
45+
}

packages/destination-actions/src/destinations/pendo-audiences/generated-types.ts

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/destination-actions/src/destinations/pendo-audiences/index.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { AudienceDestinationDefinition, IntegrationError, defaultValues } from '@segment/actions-core'
22
import type { Settings, AudienceSettings } from './generated-types'
33
import syncAudience from './syncAudience'
4-
import { createSegment, getSegment } from './functions'
5-
import { CONSTANTS } from './constants'
4+
import { getDomain, createSegment, getSegment } from './functions'
5+
import { REGIONS } from './constants'
66

77
const destination: AudienceDestinationDefinition<Settings, AudienceSettings> = {
88
name: 'Pendo Audiences',
@@ -18,12 +18,23 @@ const destination: AudienceDestinationDefinition<Settings, AudienceSettings> = {
1818
'Your Pendo Integration Key. Found in Pendo under Settings > Integrations > Integration Keys.',
1919
type: 'password',
2020
required: true
21+
},
22+
region: {
23+
label: 'Region',
24+
description: 'The region your Pendo account is hosted in.',
25+
type: 'string',
26+
required: true,
27+
choices: (Object.keys(REGIONS) as Array<keyof typeof REGIONS>).map((key) => ({
28+
label: REGIONS[key].name,
29+
value: REGIONS[key].name
30+
})),
31+
default: 'DEFAULT'
2132
}
2233
},
23-
testAuthentication: async (request) => {
24-
return await request(`${CONSTANTS.API_BASE_URL}/api/v1/feature`, {
25-
method: 'GET',
26-
skipResponseCloning: true
34+
testAuthentication: async (request, { settings }) => {
35+
const { region } = settings
36+
return await request(`${getDomain(region)}/api/v1/token/verify`, {
37+
method: 'GET'
2738
})
2839
}
2940
},
@@ -52,7 +63,8 @@ const destination: AudienceDestinationDefinition<Settings, AudienceSettings> = {
5263
async createAudience(request, createAudienceInput) {
5364
const {
5465
audienceName,
55-
audienceSettings
66+
audienceSettings,
67+
settings: { region }
5668
} = createAudienceInput
5769

5870
const segmentName = audienceSettings?.audienceName ?? audienceName
@@ -61,12 +73,12 @@ const destination: AudienceDestinationDefinition<Settings, AudienceSettings> = {
6173
throw new IntegrationError('A Pendo Segment name is required', 'MISSING_REQUIRED_FIELD', 422)
6274
}
6375

64-
const segmentId = await createSegment(request, segmentName)
76+
const segmentId = await createSegment(request, region, segmentName)
6577
return { externalId: segmentId }
6678
},
6779
async getAudience(request, getAudienceInput) {
68-
const { externalId } = getAudienceInput
69-
const segmentId = await getSegment(request, externalId)
80+
const { externalId, settings: { region } } = getAudienceInput
81+
const segmentId = await getSegment(request, region, externalId)
7082
return { externalId: segmentId }
7183
}
7284
},

0 commit comments

Comments
 (0)