Permalink
Browse files

Merge pull request #835 from Microsoft/jwilaby/#730-luis-deep-links

Added luis link support by region
  • Loading branch information...
justinwilaby committed Sep 6, 2018
2 parents dfac21e + 2c3a9a5 commit f50931a0ca23333abde889ad1e44ef44bc141e3b
@@ -7,7 +7,7 @@ import {
import { DialogService } from '../../ui/dialogs/service'; // ☣☣ careful! ☣☣
import { ConnectedServiceEditorContainer } from '../../ui/shell/explorer/servicesExplorer/connectedServiceEditor';
import { ConnectedServicePickerContainer } from '../../ui/shell/explorer/servicesExplorer';
import { combineReducers, createStore } from 'redux';
import { applyMiddleware, combineReducers, createStore } from 'redux';
import { servicesExplorerSagas } from './servicesExplorerSagas';
import azureAuth from '../reducer/azureAuthReducer';
import bot from '../reducer/bot';
@@ -18,15 +18,19 @@ import {
ConnectedServicePickerPayload,
launchConnectedServicePicker,
openAddServiceContextMenu,
openContextMenuForConnectedService
openContextMenuForConnectedService,
openServiceDeepLink
} from '../action/connectedServiceActions';
import { SharedConstants } from '@bfemulator/app-shared';
import { ServiceCodes, SharedConstants } from '@bfemulator/app-shared';
import { CommandServiceImpl } from '../../platform/commands/commandServiceImpl';
import { load, setActive } from '../action/botActions';
import { ServiceTypes } from 'botframework-config/lib/schema';
import { ServiceCodes } from '@bfemulator/app-shared';
import sagaMiddlewareFactory from 'redux-saga';
const sagaMiddleWare = sagaMiddlewareFactory();
const mockStore = createStore(combineReducers({ azureAuth, bot }), {}, applyMiddleware(sagaMiddleWare));
sagaMiddleWare.run(servicesExplorerSagas);
const mockStore = createStore(combineReducers({ azureAuth, bot }));
const mockArmToken = 'bm90aGluZw==.eyJ1cG4iOiJnbGFzZ293QHNjb3RsYW5kLmNvbSJ9.7gjdshgfdsk98458205jfds9843fjds';
jest.mock('../../ui/dialogs', () => ({
@@ -75,7 +79,7 @@ jest.mock('./azureAuthSaga', () => ({
CommandServiceImpl.remoteCall = async function (type: string) {
switch (type) {
case SharedConstants.Commands.ConnectedService.GetConnectedServicesByType:
return { services: [{ id: 'a luis service' }], code: ServiceCodes.OK};
return { services: [{ id: 'a luis service' }], code: ServiceCodes.OK };
default:
return null;
@@ -392,4 +396,42 @@ describe('The ServiceExplorerSagas', () => {
expect(result.access_token).toBe(mockArmToken);
});
});
describe('openConnectedServiceDeepLink', () => {
const mockModel = { type: ServiceTypes.Luis, appId: '1234', version: '0.1', region: 'westeurope' };
let openConnectedServiceGen;
beforeEach(() => {
const sagaIt = servicesExplorerSagas();
let i = 3;
while (i--) {
openConnectedServiceGen = sagaIt.next().value.FORK.args[1];
}
});
it('should open the correct domain for luis models in the "westeurope" region', () => {
const spy = jest.spyOn(CommandServiceImpl, 'remoteCall');
const link = `https://www.eu.luis.ai/applications/1234/versions/0.1/build`;
const action = openServiceDeepLink(mockModel as any);
openConnectedServiceGen(action).next();
expect(spy).toHaveBeenCalledWith('electron:open-external', link);
});
it('should open the correct domain for luis models in the "australiaeast" region', () => {
mockModel.region = 'australiaeast';
const spy = jest.spyOn(CommandServiceImpl, 'remoteCall');
const link = `https://www.au.luis.ai/applications/1234/versions/0.1/build`;
const action = openServiceDeepLink(mockModel as any);
openConnectedServiceGen(action).next();
expect(spy).toHaveBeenCalledWith('electron:open-external', link);
});
it('should open the correct domain for luis models in the "westus" region', () => {
mockModel.region = 'westus';
const spy = jest.spyOn(CommandServiceImpl, 'remoteCall');
const link = `https://www.luis.ai/applications/1234/versions/0.1/build`;
const action = openServiceDeepLink(mockModel as any);
openConnectedServiceGen(action).next();
expect(spy).toHaveBeenCalledWith('electron:open-external', link);
});
});
});
@@ -254,8 +254,22 @@ function* launchConnectedServiceEditor(action: ConnectedServiceAction<ConnectedS
}
function openLuisDeepLink(luisService: ILuisService): Promise<any> {
const { appId, version } = luisService;
const link = `https://www.luis.ai/applications/${appId}/versions/${version}/build`;
const { appId, version, region } = luisService;
let regionPrefix: string;
switch (region) {
case 'westeurope':
regionPrefix = 'eu.';
break;
case 'australiaeast':
regionPrefix = 'au.';
break;
default:
regionPrefix = '';
break;
}
const link = `https://www.${regionPrefix}luis.ai/applications/${appId}/versions/${version}/build`;
return CommandServiceImpl.remoteCall(SharedConstants.Commands.Electron.OpenExternal, link);
}
@@ -32,6 +32,7 @@
"url": "https://github.com/Microsoft/BotFramework-Emulator"
},
"jest": {
"setupTestFrameworkScriptFile": "../../../testSetup.js",
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
@@ -0,0 +1,138 @@
import { combineReducers, createStore } from 'redux';
import { bot } from '../botData/reducers/bot';
import { SharedConstants } from '@bfemulator/app-shared';
import { BotConfigWithPathImpl } from '@bfemulator/sdk-shared';
import * as helpers from '../botHelpers';
import { getBotInfoByPath, loadBotWithRetry, pathExistsInRecentBots } from '../botHelpers';
import { CommandRegistryImpl } from '@bfemulator/sdk-shared/built';
import { registerCommands } from './botCommands';
import { botProjectFileWatcher } from '../watchers';
import { mainWindow } from '../main';
import { State } from '../botData/state';
import * as store from '../botData/store';
import { setActive } from '../botData/actions/botActions';
import { emulator } from '../emulator';
let mockStore;
(store as any).getStore = function () {
return mockStore || (mockStore = createStore(combineReducers({ bot })));
};
jest.mock('../botHelpers', () => ({
saveBot: async () => void(0),
patchBotsJson: async () => true,
pathExistsInRecentBots: () => true,
getBotInfoByPath: () => ({ secret: 'secret' }),
loadBotWithRetry: () => mockBot,
getActiveBot: () => mockBot
}
));
jest.mock('../utils/ensureStoragePath', () => ({
ensureStoragePath: () => ''
}));
jest.mock('../emulator', () => ({
emulator: {
framework: {
server: {
botEmulator: {
facilities: {
endpoints: {
reset: () => null,
push: () => null
}
}
}
}
}
}
}));
const mockCommandRegistry = new CommandRegistryImpl();
const mockBot = BotConfigWithPathImpl.fromJSON({
'path': 'some/path',
'name': 'AuthBot',
'description': '',
'secretKey': '',
'services': [
{
'appId': '4f8fde3f-48d3-4d8a-a954-393efe39809e',
'id': 'cded37c0-83f2-11e8-ac6d-b7172cd24b28',
'type': 'endpoint',
'appPassword': 'REDACTED',
'endpoint': 'http://localhost:55697/api/messages',
'name': 'authsample'
}
]
} as any);
registerCommands(mockCommandRegistry);
jest.mock('../main', () => ({
mainWindow: {
commandService: {
call: async () => true
}
}
}));
jest.mock('../watchers', () => ({
botProjectFileWatcher: {
watch: () => true
}
}));
const { Bot } = SharedConstants.Commands;
describe('The botCommands', () => {
it('should create/save a new bot', async () => {
const botToSave = BotConfigWithPathImpl.fromJSON(mockBot as any);
const patchBotInfoSpy = jest.spyOn(helpers, 'patchBotsJson');
const saveBotSpy = jest.spyOn(helpers, 'saveBot');
const mockBotInfo = {
path: botToSave.path,
displayName: 'AuthBot',
secret: 'secret'
};
const command = mockCommandRegistry.getCommand(Bot.Create);
const result = await command.handler(botToSave, 'secret');
expect(patchBotInfoSpy).toHaveBeenCalledWith(botToSave.path, mockBotInfo);
expect(saveBotSpy).toHaveBeenCalledWith(botToSave);
expect(result).toEqual(botToSave);
});
it('should open a bot', async () => {
const pathExistsInRecentBotsSpy = jest.spyOn(helpers, 'pathExistsInRecentBots').mockReturnValue(true);
const getBotInfoByPathSpy = jest.spyOn(helpers, 'getBotInfoByPath').mockReturnValue({ secret: 'secret' });
const loadBotWithRetrySpy = jest.spyOn(helpers, 'loadBotWithRetry').mockResolvedValue(mockBot);
const command = mockCommandRegistry.getCommand(Bot.Open);
const result = await command.handler('bot/path', 'secret');
expect(pathExistsInRecentBotsSpy).toHaveBeenCalledWith('bot/path');
expect(getBotInfoByPathSpy).toHaveBeenCalledWith('bot/path');
expect(loadBotWithRetrySpy).toHaveBeenCalledWith('bot/path', 'secret');
expect(result).toEqual(mockBot);
});
it('should set the active bot', async () => {
const botProjectFileWatcherSpy = jest.spyOn(botProjectFileWatcher, 'watch');
const commandServiceSpy = jest.spyOn(mainWindow.commandService, 'call');
const command = mockCommandRegistry.getCommand(Bot.SetActive);
const result = await command.handler(mockBot);
expect(botProjectFileWatcherSpy).toHaveBeenCalledWith(mockBot.path);
expect(commandServiceSpy).toHaveBeenCalledWith(Bot.RestartEndpointService);
const state: State = store.getStore().getState() as State;
expect(state.bot.activeBot).toEqual(mockBot);
expect(state.bot.currentBotDirectory).toBe('some');
expect(result).toEqual('some');
});
it('should restart the endpoint service', async () => {
store.getStore().dispatch(setActive(mockBot));
const resetSpy = jest.spyOn(emulator.framework.server.botEmulator.facilities.endpoints, 'reset');
const pushSpy = jest.spyOn(emulator.framework.server.botEmulator.facilities.endpoints, 'push');
const command = mockCommandRegistry.getCommand(Bot.RestartEndpointService);
const result = await command.handler();
expect(resetSpy).toHaveBeenCalled();
expect(pushSpy).toHaveBeenCalled();
expect(result).toBeUndefined();
});
});
@@ -51,38 +51,33 @@ import * as path from 'path';
import { getStore } from '../botData/store';
import { botProjectFileWatcher, chatWatcher, transcriptsWatcher } from '../watchers';
const store = getStore();
/** Registers bot commands */
export function registerCommands(commandRegistry: CommandRegistryImpl) {
const { Bot } = SharedConstants.Commands;
// ---------------------------------------------------------------------------
// Create a bot
commandRegistry.registerCommand(Bot.Create,
async (
bot: BotConfigWithPath,
secret: string
): Promise<BotConfigWithPath> => {
// getStore and add bot entry to bots.json
const botsJsonEntry: BotInfo = {
path: bot.path,
displayName: getBotDisplayName(bot),
secret
};
await patchBotsJson(bot.path, botsJsonEntry);
commandRegistry.registerCommand(Bot.Create, async (bot: BotConfigWithPath, secret: string)
: Promise<BotConfigWithPath> => {
// getStore and add bot entry to bots.json
const botsJsonEntry: BotInfo = {
path: bot.path,
displayName: getBotDisplayName(bot),
secret
};
await patchBotsJson(bot.path, botsJsonEntry);
// save the bot
try {
await saveBot(bot);
} catch (e) {
// TODO: make sure these are surfaced on the client side and caught so we can act on them
console.error(`${Bot.Create}: Error trying to save bot: ${e}`);
throw e;
}
// save the bot
try {
await saveBot(bot);
} catch (e) {
// TODO: make sure these are surfaced on the client side and caught so we can act on them
console.error(`${Bot.Create}: Error trying to save bot: ${e}`);
throw e;
}
return bot;
});
return bot;
});
// ---------------------------------------------------------------------------
// Save bot file and cause a bots list write
@@ -92,36 +87,33 @@ export function registerCommands(commandRegistry: CommandRegistryImpl) {
// ---------------------------------------------------------------------------
// Opens a bot file at specified path and returns the bot
commandRegistry.registerCommand(Bot.Open,
async (
botPath: string,
secret?: string
): Promise<BotConfigWithPath> => {
// try to get the bot secret from bots.json
const botInfo = pathExistsInRecentBots(botPath) ? getBotInfoByPath(botPath) : null;
if (botInfo && botInfo.secret) {
secret = botInfo.secret;
}
commandRegistry.registerCommand(Bot.Open, async (botPath: string, secret?: string)
: Promise<BotConfigWithPath> => {
// try to get the bot secret from bots.json
const botInfo = pathExistsInRecentBots(botPath) ? getBotInfoByPath(botPath) : null;
if (botInfo && botInfo.secret) {
secret = botInfo.secret;
}
// load the bot (decrypt with secret if we were able to get it)
let bot: BotConfigWithPath;
try {
bot = await loadBotWithRetry(botPath, secret);
} catch (e) {
const errMessage = `Failed to open the bot with error: ${e.message}`;
await Electron.dialog.showMessageBox(mainWindow.browserWindow, {
type: 'error',
message: errMessage,
});
throw new Error(errMessage);
}
if (!bot) {
// user couldn't provide correct secret, abort
throw new Error('No secret provided to decrypt encrypted bot.');
}
// load the bot (decrypt with secret if we were able to get it)
let bot: BotConfigWithPath;
try {
bot = await loadBotWithRetry(botPath, secret);
} catch (e) {
const errMessage = `Failed to open the bot with error: ${e.message}`;
await Electron.dialog.showMessageBox(mainWindow.browserWindow, {
type: 'error',
message: errMessage,
});
throw new Error(errMessage);
}
if (!bot) {
// user couldn't provide correct secret, abort
throw new Error('No secret provided to decrypt encrypted bot.');
}
return bot;
});
return bot;
});
// ---------------------------------------------------------------------------
// Set active bot
@@ -130,6 +122,7 @@ export function registerCommands(commandRegistry: CommandRegistryImpl) {
await botProjectFileWatcher.watch(bot.path);
// set active bot and active directory
const store = getStore();
const botDirectory = path.dirname(bot.path);
store.dispatch(BotActions.setActive(bot));
store.dispatch(BotActions.setDirectory(botDirectory));
@@ -181,6 +174,7 @@ export function registerCommands(commandRegistry: CommandRegistryImpl) {
// Close active bot (called from client-side)
commandRegistry.registerCommand(Bot.Close, (): void => {
botProjectFileWatcher.unwatch();
const store = getStore();
store.dispatch(BotActions.close());
});
Oops, something went wrong.

0 comments on commit f50931a

Please sign in to comment.