Skip to content

Commit 1cdb41d

Browse files
OEvgenycwhitten
andauthored
fix: avoid cross-geo calls (#9613)
* WIP: avoid cross-geo calls * Use cluster category for credentials insead of hardcoded URLs * WIP publish adjustments * remove tests * fix more tests * handle optional cluster category * make ts happy --------- Co-authored-by: Chris Whitten <[email protected]>
1 parent cce5b36 commit 1cdb41d

File tree

7 files changed

+102
-177
lines changed

7 files changed

+102
-177
lines changed

Composer/packages/electron-server/src/main.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,11 @@ async function run() {
343343
};
344344
});
345345

346+
ipcMain.handle('deeplink', async (evt, url: string) => {
347+
const deeplink = parseDeepLinkUrl(url);
348+
await getMainWindow()?.webContents.loadURL(getBaseUrl() + deeplink);
349+
});
350+
346351
await main();
347352
setTimeout(() => startApp(signalThatMainWindowIsShowing), 500);
348353
await initApp();

Composer/packages/server/src/externalContentProvider/powerVirtualAgentsProvider.ts

Lines changed: 36 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ export const PVA_GOV_APP_ID = '9315aedd-209b-43b3-b149-2abff6a95d59';
2222
export const PVA_GCC_HIGH_APP_ID = '69c6e40c-465f-4154-987d-da5cba10734e';
2323

2424
export type PowerVirtualAgentsMetadata = IContentProviderMetadata & {
25+
clusterCategory?:
26+
| 'Dev'
27+
| 'Prv'
28+
| 'Test'
29+
| 'Preprod'
30+
| 'FirstRelease'
31+
| 'Prod'
32+
| 'Gov'
33+
| 'High'
34+
| 'DoD'
35+
| 'Mooncake'
36+
| 'Ex'
37+
| 'Rx';
2538
baseUrl: string;
2639
botId: string;
2740
dialogId?: string;
@@ -32,22 +45,35 @@ export type PowerVirtualAgentsMetadata = IContentProviderMetadata & {
3245
};
3346

3447
const getAuthCredentials = (baseUrl: string, metadata: PowerVirtualAgentsMetadata) => {
48+
const clusterCategory =
49+
(process.env.COMPOSER_PVA_CLUSTER as typeof metadata.clusterCategory) ?? metadata.clusterCategory;
3550
const url = new URL(baseUrl);
36-
if (url.hostname.includes('.int.') || url.hostname.includes('.ppe.')) {
51+
if (
52+
(clusterCategory && ['Test', 'Preprod', 'Dev'].includes(clusterCategory)) ||
53+
url.hostname.includes('.int.') ||
54+
url.hostname.includes('.ppe.') ||
55+
url.hostname.includes('.test.')
56+
) {
3757
log('Using INT / PPE auth credentials.');
3858
return {
3959
clientId: COMPOSER_1P_APP_ID,
4060
scopes: [`${PVA_TEST_APP_ID}/.default`],
4161
targetResource: PVA_TEST_APP_ID,
4262
};
43-
} else if (url.hostname.includes('gcc.api.powerva.microsoft.us')) {
63+
} else if (
64+
(clusterCategory && ['Gov'].includes(clusterCategory)) ||
65+
url.hostname.includes('gcc.api.powerva.microsoft.us')
66+
) {
4467
log('Using GCC auth credentials.');
4568
return {
4669
clientId: COMPOSER_1P_APP_ID,
4770
scopes: [`${PVA_GOV_APP_ID}/.default`],
4871
targetResource: PVA_GOV_APP_ID,
4972
};
50-
} else if (url.hostname.includes('high.api.powerva.microsoft.us')) {
73+
} else if (
74+
(clusterCategory && ['High'].includes(clusterCategory)) ||
75+
url.hostname.includes('high.api.powerva.microsoft.us')
76+
) {
5177
log('Using GCC High auth credentials.');
5278
return {
5379
authority: `https://login.microsoftonline.us/${metadata.tenantId}`,
@@ -56,55 +82,14 @@ const getAuthCredentials = (baseUrl: string, metadata: PowerVirtualAgentsMetadat
5682
targetResource: PVA_GCC_HIGH_APP_ID,
5783
};
5884
}
59-
log('Using PROD auth credentials.');
85+
log(`Using PROD auth credentials.\nCategory: ${clusterCategory}\nURL: ${baseUrl}`);
6086
return {
6187
clientId: COMPOSER_1P_APP_ID,
6288
scopes: [`${PVA_PROD_APP_ID}/.default`],
6389
targetResource: PVA_PROD_APP_ID,
6490
};
6591
};
6692

67-
const getBaseUrl = () => {
68-
const pvaEnv = (process.env.COMPOSER_PVA_ENV || '').toLowerCase();
69-
switch (pvaEnv) {
70-
case 'prod': {
71-
const url = 'https://powerva.microsoft.com/api/botmanagement/v1';
72-
log('PROD env detected, grabbing PVA content from %s', url);
73-
return url;
74-
}
75-
76-
case 'ppe': {
77-
const url = 'https://bots.ppe.customercareintelligence.net/api/botmanagement/v1';
78-
log('PPE env detected, grabbing PVA content from %s', url);
79-
return url;
80-
}
81-
82-
case 'int': {
83-
const url = 'https://bots.int.customercareintelligence.net/api/botmanagement/v1';
84-
log('INT env detected, grabbing PVA content from %s', url);
85-
return url;
86-
}
87-
88-
case 'gcc': {
89-
const url = 'https://gcc.api.powerva.microsoft.us/api/botmanagement/v1';
90-
log('GCC env detected, grabbing PVA content from %s', url);
91-
return url;
92-
}
93-
94-
case 'gcc-high': {
95-
const url = 'https://high.api.powerva.microsoft.us/api/botmanagement/v1';
96-
log('GCC High env detected, grabbing PVA content from %s', url);
97-
return url;
98-
}
99-
100-
default: {
101-
const url = 'https://bots.int.customercareintelligence.net/api/botmanagement/v1';
102-
log('No env flag detected, grabbing PVA content from %s', url);
103-
return url;
104-
}
105-
}
106-
};
107-
10893
function prettyPrintError(err: string | Error): string {
10994
if (typeof err === 'string') {
11095
return err;
@@ -177,7 +162,7 @@ export class PowerVirtualAgentsProvider extends ExternalContentProvider<PowerVir
177162
try {
178163
// login to the 1P app and get an access token
179164
const { baseUrl } = this.metadata;
180-
const authCredentials = getAuthCredentials(baseUrl || getBaseUrl(), this.metadata);
165+
const authCredentials = getAuthCredentials(baseUrl, this.metadata);
181166
const accessToken = await authService.getAccessToken(authCredentials);
182167
if (accessToken === '') {
183168
throw 'User cancelled login flow.';
@@ -190,7 +175,7 @@ export class PowerVirtualAgentsProvider extends ExternalContentProvider<PowerVir
190175

191176
private getContentUrl(): string {
192177
const { envId, baseUrl, botId } = this.metadata;
193-
return `${baseUrl || getBaseUrl()}/environments/${envId}/bots/${botId}/composer/content?includeTopics=true`;
178+
return `${baseUrl}/environments/${envId}/bots/${botId}/composer/content?includeTopics=true`;
194179
}
195180

196181
private async getRequestHeaders() {
@@ -206,7 +191,7 @@ export class PowerVirtualAgentsProvider extends ExternalContentProvider<PowerVir
206191
private getDeepLink(): string {
207192
// use metadata (if provided) to create a deep link to a specific dialog / trigger / action etc. after opening bot.
208193
let deepLink = '';
209-
const { dialogId, triggerId, actionId = '' } = this.metadata;
194+
const { dialogId, triggerId, actionId = '', clusterCategory } = this.metadata;
210195

211196
if (dialogId) {
212197
deepLink += `dialogs/${dialogId}`;
@@ -219,6 +204,9 @@ export class PowerVirtualAgentsProvider extends ExternalContentProvider<PowerVir
219204
`"${actionId}"`,
220205
)}]`;
221206
}
207+
if (clusterCategory) {
208+
deepLink += deepLink.includes('?') ? '&' : '?' + `cluster=${clusterCategory}`;
209+
}
222210
// base64 encode to make parsing on the client side easier
223211
return Buffer.from(deepLink, 'utf-8').toString('base64');
224212
}

extensions/pvaPublish/src/node/constants.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@ export const AUTH_CREDENTIALS = {
1515
// electron auth flow
1616
targetResource: 'a522f059-bb65-47c0-8934-7db6e5286414',
1717
},
18-
PPE: {
19-
clientId: COMPOSER_1P_APP_ID,
20-
scopes: ['a522f059-bb65-47c0-8934-7db6e5286414/.default'],
21-
targetResource: 'a522f059-bb65-47c0-8934-7db6e5286414',
22-
},
2318
PROD: {
2419
clientId: COMPOSER_1P_APP_ID,
2520
scopes: ['96ff4394-9197-43aa-b393-6a41652e21f8/.default'],
@@ -36,11 +31,3 @@ export const AUTH_CREDENTIALS = {
3631
targetResource: '69c6e40c-465f-4154-987d-da5cba10734e',
3732
},
3833
};
39-
40-
export const BASE_URLS = {
41-
INT: 'https://bots.int.customercareintelligence.net/',
42-
PPE: 'https://bots.ppe.customercareintelligence.net/',
43-
PROD: 'https://powerva.microsoft.com/',
44-
GCC: 'https://gcc.api.powerva.microsoft.us/',
45-
GCC_HIGH: 'https://high.api.powerva.microsoft.us/',
46-
};

extensions/pvaPublish/src/node/publish.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { RequestInit } from 'node-fetch';
1111

1212
import fetch from './fetch';
1313
import { PVAPublishJob, PublishConfig, UserIdentity, PublishState, PublishHistory, PullResponse } from './types';
14-
import { getAuthCredentials, getBaseUrl } from './utils';
14+
import { getAuthCredentials } from './utils';
1515
import { logger } from './logger';
1616
import { API_VERSION } from './constants';
1717

@@ -35,14 +35,18 @@ export const publish = async (
3535
envId,
3636
tenantId,
3737
deleteMissingDependencies = false, // publish behavior
38+
clusterCategory,
3839
} = config;
3940
const { comment = '' } = metadata;
4041

4142
try {
4243
logger.log('Starting publish to Power Virtual Agents.');
4344
// authenticate with PVA
44-
const base = baseUrl || getBaseUrl();
45-
const creds = getAuthCredentials(base, tenantId);
45+
const base = baseUrl;
46+
if (!base) {
47+
throw new Error('Base URL is not supplied in published target');
48+
}
49+
const creds = getAuthCredentials(base, tenantId, clusterCategory);
4650
const accessToken = await getAccessToken(creds);
4751

4852
// write the .zip to a buffer in memory
@@ -145,6 +149,7 @@ export const getStatus = async (
145149
botId,
146150
envId,
147151
tenantId,
152+
clusterCategory,
148153
} = config;
149154
const botProjectId = project.id || '';
150155

@@ -161,8 +166,11 @@ export const getStatus = async (
161166

162167
try {
163168
// authenticate with PVA
164-
const base = baseUrl || getBaseUrl();
165-
const creds = getAuthCredentials(base, tenantId);
169+
const base = baseUrl;
170+
if (!base) {
171+
throw new Error('Base URL is not supplied in published target');
172+
}
173+
const creds = getAuthCredentials(base, tenantId, clusterCategory);
166174
const accessToken = await getAccessToken(creds);
167175

168176
// check the status for the publish job
@@ -218,12 +226,13 @@ export const history = async (
218226
botId,
219227
envId,
220228
tenantId,
229+
clusterCategory,
221230
} = config;
222231

223232
try {
224233
// authenticate with PVA
225-
const base = baseUrl || getBaseUrl();
226-
const creds = getAuthCredentials(base, tenantId);
234+
const base = baseUrl;
235+
const creds = getAuthCredentials(base, tenantId, clusterCategory);
227236
const accessToken = await getAccessToken(creds);
228237

229238
// get the publish history for the bot
@@ -253,11 +262,12 @@ export const pull = async (
253262
botId,
254263
envId,
255264
tenantId,
265+
clusterCategory,
256266
} = config;
257267
try {
258268
// authenticate with PVA
259-
const base = baseUrl || getBaseUrl();
260-
const creds = getAuthCredentials(base, tenantId);
269+
const base = baseUrl;
270+
const creds = getAuthCredentials(base, tenantId, clusterCategory);
261271
const accessToken = await getAccessToken(creds);
262272

263273
// fetch zip containing bot content

extensions/pvaPublish/src/node/types.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@
33

44
import { PublishResult } from '@botframework-composer/types';
55

6+
export type ClusterCategory =
7+
| 'Dev'
8+
| 'Prv'
9+
| 'Test'
10+
| 'Preprod'
11+
| 'FirstRelease'
12+
| 'Prod'
13+
| 'Gov'
14+
| 'High'
15+
| 'DoD'
16+
| 'Mooncake'
17+
| 'Ex'
18+
| 'Rx';
19+
620
export type PVAPublishJob = {
721
comment: string;
822
diagnostics: DiagnosticInfo[];
@@ -38,6 +52,7 @@ export type PublishConfig = {
3852
fullSettings: any;
3953
profileName: string;
4054
tenantId: string;
55+
clusterCategory: ClusterCategory;
4156
};
4257

4358
export type PublishState =

extensions/pvaPublish/src/node/utils.spec.ts

Lines changed: 3 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,8 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import { AUTH_CREDENTIALS, BASE_URLS } from './constants';
5-
import { getAuthCredentials, getBaseUrl } from './utils';
6-
7-
describe('should return the proper PVA base URL for the environment', () => {
8-
let envBackup;
9-
beforeAll(() => {
10-
envBackup = { ...process.env };
11-
});
12-
13-
beforeEach(() => {
14-
Object.assign(process.env, { COMPOSER_PVA_PUBLISH_ENV: '' });
15-
});
16-
17-
afterAll(() => {
18-
Object.assign(process.env, envBackup); // restore the platform
19-
});
20-
21-
it('fallback to prod', () => {
22-
expect(getBaseUrl()).toBe(BASE_URLS.PROD);
23-
});
24-
25-
it('int', () => {
26-
Object.assign(process.env, { COMPOSER_PVA_PUBLISH_ENV: 'INT' });
27-
expect(getBaseUrl()).toBe(BASE_URLS.INT);
28-
});
29-
30-
it('ppe', () => {
31-
Object.assign(process.env, { COMPOSER_PVA_PUBLISH_ENV: 'PPE' });
32-
expect(getBaseUrl()).toBe(BASE_URLS.PPE);
33-
});
34-
35-
it('prod', () => {
36-
Object.assign(process.env, { COMPOSER_PVA_PUBLISH_ENV: 'PROD' });
37-
expect(getBaseUrl()).toBe(BASE_URLS.PROD);
38-
});
39-
40-
it('gcc', () => {
41-
Object.assign(process.env, { COMPOSER_PVA_PUBLISH_ENV: 'GCC' });
42-
expect(getBaseUrl()).toBe(BASE_URLS.GCC);
43-
});
44-
45-
it('gcc high', () => {
46-
Object.assign(process.env, { COMPOSER_PVA_PUBLISH_ENV: 'GCC-HIGH' });
47-
expect(getBaseUrl()).toBe(BASE_URLS.GCC_HIGH);
48-
});
49-
});
4+
import { AUTH_CREDENTIALS } from './constants';
5+
import { getAuthCredentials } from './utils';
506

517
describe('it should return the proper PVA auth parameters for the base URL', () => {
528
it('fallback to prod', () => {
@@ -64,7 +20,7 @@ describe('it should return the proper PVA auth parameters for the base URL', ()
6420

6521
it('ppe', () => {
6622
const url = 'https://bots.ppe.customercareintelligence.net/api/botmanagement/v1';
67-
expect(getAuthCredentials(url)).toEqual(AUTH_CREDENTIALS.PPE);
23+
expect(getAuthCredentials(url)).toEqual(AUTH_CREDENTIALS.INT);
6824
});
6925

7026
it('prod', () => {

0 commit comments

Comments
 (0)