Skip to content

Commit e75f1dc

Browse files
enrich Memora store dropdown with displayName via individual store detail calls
1 parent 4e0e869 commit e75f1dc

File tree

2 files changed

+104
-8
lines changed

2 files changed

+104
-8
lines changed

packages/destination-actions/src/destinations/memora/upsertProfile/__tests__/index.test.ts

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ describe('Memora.upsertProfile', () => {
653653

654654
describe('dynamicFields', () => {
655655
describe('memora_store', () => {
656-
it('should fetch and return memory stores from Control Plane', async () => {
656+
it('should fetch and return memory stores with details from Control Plane', async () => {
657657
nock(BASE_URL)
658658
.get(`/${API_VERSION}/ControlPlane/Stores?pageSize=100&orderBy=ASC`)
659659
.matchHeader('X-Pre-Auth-Context', 'AC1234567890')
@@ -665,18 +665,71 @@ describe('Memora.upsertProfile', () => {
665665
}
666666
})
667667

668+
nock(BASE_URL)
669+
.get(`/${API_VERSION}/ControlPlane/Stores/store-1`)
670+
.matchHeader('X-Pre-Auth-Context', 'AC1234567890')
671+
.reply(200, { id: 'store-1', displayName: 'Store One' })
672+
673+
nock(BASE_URL)
674+
.get(`/${API_VERSION}/ControlPlane/Stores/store-2`)
675+
.matchHeader('X-Pre-Auth-Context', 'AC1234567890')
676+
.reply(200, { id: 'store-2', displayName: 'Store Two' })
677+
678+
nock(BASE_URL)
679+
.get(`/${API_VERSION}/ControlPlane/Stores/store-3`)
680+
.matchHeader('X-Pre-Auth-Context', 'AC1234567890')
681+
.reply(200, { id: 'store-3', displayName: 'Store Three' })
682+
668683
const result = (await testDestination.testDynamicField('upsertProfile', 'memora_store', {
669684
settings: defaultSettings,
670685
payload: {}
671686
})) as any
672687

673688
expect(result?.choices).toEqual([
674-
{ label: 'store-1', value: 'store-1' },
675-
{ label: 'store-2', value: 'store-2' },
676-
{ label: 'store-3', value: 'store-3' }
689+
{ label: 'Store One', value: 'store-1' },
690+
{ label: 'Store Two', value: 'store-2' },
691+
{ label: 'Store Three', value: 'store-3' }
677692
])
678693
})
679694

695+
it('should fall back to store id when displayName is empty', async () => {
696+
nock(BASE_URL)
697+
.get(`/${API_VERSION}/ControlPlane/Stores?pageSize=100&orderBy=ASC`)
698+
.reply(200, { stores: ['store-no-name'] })
699+
700+
nock(BASE_URL)
701+
.get(`/${API_VERSION}/ControlPlane/Stores/store-no-name`)
702+
.reply(200, { id: 'store-no-name', displayName: '' })
703+
704+
const result = (await testDestination.testDynamicField('upsertProfile', 'memora_store', {
705+
settings: defaultSettings,
706+
payload: {}
707+
})) as any
708+
709+
expect(result?.choices).toEqual([{ label: 'store-no-name', value: 'store-no-name' }])
710+
})
711+
712+
it('should not include X-Pre-Auth-Context header in store detail requests when twilioAccount is not set', async () => {
713+
const settingsNoTwilio = { username: 'test-api-key', password: 'test-api-secret' }
714+
715+
nock(BASE_URL)
716+
.get(`/${API_VERSION}/ControlPlane/Stores?pageSize=100&orderBy=ASC`)
717+
.matchHeader('X-Pre-Auth-Context', (val) => val === undefined)
718+
.reply(200, { stores: ['store-1'] })
719+
720+
nock(BASE_URL)
721+
.get(`/${API_VERSION}/ControlPlane/Stores/store-1`)
722+
.matchHeader('X-Pre-Auth-Context', (val) => val === undefined)
723+
.reply(200, { id: 'store-1', displayName: 'Store One' })
724+
725+
const result = (await testDestination.testDynamicField('upsertProfile', 'memora_store', {
726+
settings: settingsNoTwilio,
727+
payload: {}
728+
})) as any
729+
730+
expect(result?.choices).toEqual([{ label: 'Store One', value: 'store-1' }])
731+
})
732+
680733
it('should handle empty stores list', async () => {
681734
nock(BASE_URL)
682735
.get(`/${API_VERSION}/ControlPlane/Stores?pageSize=100&orderBy=ASC`)
@@ -693,6 +746,26 @@ describe('Memora.upsertProfile', () => {
693746
expect(result?.choices).toEqual([])
694747
})
695748

749+
it('should return error when a store detail request fails', async () => {
750+
nock(BASE_URL)
751+
.get(`/${API_VERSION}/ControlPlane/Stores?pageSize=100&orderBy=ASC`)
752+
.reply(200, { stores: ['store-1'] })
753+
754+
nock(BASE_URL)
755+
.get(`/${API_VERSION}/ControlPlane/Stores/store-1`)
756+
.reply(500, { message: 'Internal server error' })
757+
758+
const result = (await testDestination.testDynamicField('upsertProfile', 'memora_store', {
759+
settings: defaultSettings,
760+
payload: {}
761+
})) as any
762+
763+
expect(result?.choices).toEqual([])
764+
expect(result?.error).toBeDefined()
765+
expect(result?.error?.message).toContain('Unable to fetch memora stores')
766+
expect(result?.error?.code).toBe('FETCH_ERROR')
767+
})
768+
696769
it('should return error message when API call fails', async () => {
697770
nock(BASE_URL)
698771
.get(`/${API_VERSION}/ControlPlane/Stores?pageSize=100&orderBy=ASC`)
@@ -706,6 +779,7 @@ describe('Memora.upsertProfile', () => {
706779
expect(result?.choices).toEqual([])
707780
expect(result?.error).toBeDefined()
708781
expect(result?.error?.message).toContain('Unable to fetch memora stores')
782+
expect(result?.error?.message).toContain('Enter the memora store ID manually.')
709783
expect(result?.error?.code).toBe('FETCH_ERROR')
710784
})
711785
})

packages/destination-actions/src/destinations/memora/upsertProfile/index.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,11 @@ interface MemoraStoresResponse {
305305
}
306306
}
307307

308+
interface MemoraStoreDetails {
309+
displayName: string
310+
id: string
311+
}
312+
308313
interface TraitDefinition {
309314
dataType: string
310315
description?: string
@@ -378,9 +383,26 @@ async function fetchMemoraStores(request: RequestClient, settings: Settings) {
378383

379384
const stores = response?.data?.stores || []
380385

381-
const choices = stores.map((storeId: string) => ({
382-
label: storeId,
383-
value: storeId
386+
// This is not the most efficient way to get store details, but the Control Plane API does not currently provide an endpoint to list stores with their details in a single call.
387+
// We need to make individual calls to get store details in order to display more information in the dropdown (e.g. store name).
388+
// Fortunately, most accounts will have a small number of stores (max 5), so this should not be a major performance issue. If we find that this is causing performance problems, we can consider caching store details or adding an endpoint to the Control Plane API to list stores with their details.
389+
const memoraStores = await Promise.all(
390+
stores.map((storeId: string) => {
391+
return request<MemoraStoreDetails>(`${BASE_URL}/${API_VERSION}/ControlPlane/Stores/${storeId}`, {
392+
method: 'GET',
393+
headers: {
394+
...(settings.twilioAccount && { 'X-Pre-Auth-Context': settings.twilioAccount })
395+
},
396+
username: settings.username,
397+
password: settings.password,
398+
skipResponseCloning: true
399+
})
400+
})
401+
)
402+
403+
const choices = memoraStores.map((store) => ({
404+
label: store.data?.displayName || store.data?.id,
405+
value: store.data?.id
384406
}))
385407

386408
return {
@@ -391,7 +413,7 @@ async function fetchMemoraStores(request: RequestClient, settings: Settings) {
391413
return {
392414
choices: [],
393415
error: {
394-
message: 'Unable to fetch memora stores. You can still manually enter a memora store ID.',
416+
message: 'Unable to fetch memora stores. Enter the memora store ID manually.',
395417
code: 'FETCH_ERROR'
396418
}
397419
}

0 commit comments

Comments
 (0)