Skip to content

Commit

Permalink
Merge f43eb80 into 8201114
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyanziano committed Jan 31, 2020
2 parents 8201114 + f43eb80 commit 399450a
Show file tree
Hide file tree
Showing 15 changed files with 243 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Added
- [client/main] Added Ngrok Status Viewer in PR [2032](https://github.com/microsoft/BotFramework-Emulator/pull/2032)
- [client/main] Changed conversation infrastructure to use Web Sockets to communicate with Web Chat in PR [2034](https://github.com/microsoft/BotFramework-Emulator/pull/2034)
- [client/main] Added new telemetry events and properties in PR [2063](https://github.com/microsoft/BotFramework-Emulator/pull/2063)

## Fixed
- [client] Hid services pane by default in PR [2059](https://github.com/microsoft/BotFramework-Emulator/pull/2059)
Expand Down
9 changes: 7 additions & 2 deletions packages/app/client/src/state/sagas/azureAuthSaga.spec.ts
Expand Up @@ -198,7 +198,9 @@ describe('The azureAuthSaga', () => {
ct++;
}
expect(ct).toBe(5);
expect(remoteCallSpy).toHaveBeenCalledWith(SharedConstants.Commands.Telemetry.TrackEvent, 'signIn_failure');
expect(remoteCallSpy).toHaveBeenCalledWith(SharedConstants.Commands.Telemetry.TrackEvent, 'azure_signIn', {
success: false,
});
});

it('should contain 6 steps when the Azure login dialog prompt is confirmed and auth succeeds', async () => {
Expand Down Expand Up @@ -267,7 +269,10 @@ describe('The azureAuthSaga', () => {
}
expect(ct).toBe(6);
expect(store.getState().azureAuth.access_token).toBe('a valid access_token');
expect(remoteCallSpy).toHaveBeenCalledWith(SharedConstants.Commands.Telemetry.TrackEvent, 'signIn_success');
expect(remoteCallSpy).toHaveBeenCalledWith(SharedConstants.Commands.Telemetry.TrackEvent, 'azure_signIn', {
persistLogin: true,
success: true,
});
});
});
});
6 changes: 4 additions & 2 deletions packages/app/client/src/state/sagas/azureAuthSaga.ts
Expand Up @@ -74,10 +74,12 @@ export class AzureAuthSaga {
PersistAzureLoginChanged,
persistLogin
);
AzureAuthSaga.commandService.remoteCall(TrackEvent, 'signIn_success').catch(_e => void 0);
AzureAuthSaga.commandService
.remoteCall(TrackEvent, 'azure_signIn', { persistLogin: !!persistLogin, success: true })
.catch(_e => void 0);
} else {
yield DialogService.showDialog(action.payload.loginFailedDialog);
AzureAuthSaga.commandService.remoteCall(TrackEvent, 'signIn_failure').catch(_e => void 0);
AzureAuthSaga.commandService.remoteCall(TrackEvent, 'azure_signIn', { success: false }).catch(_e => void 0);
}
yield put(azureArmTokenDataChanged(azureAuth.access_token));
return azureAuth;
Expand Down
20 changes: 13 additions & 7 deletions packages/app/client/src/state/sagas/botSagas.ts
Expand Up @@ -168,14 +168,20 @@ export class BotSagas {

// telemetry
if (!action.payload.isFromBotFile) {
BotSagas.commandService.remoteCall(SharedConstants.Commands.Telemetry.TrackEvent, 'bot_open', {
numOfServices: 0,
source: 'url',
});
}
if (!isLocalHostUrl(action.payload.endpoint)) {
BotSagas.commandService.remoteCall(SharedConstants.Commands.Telemetry.TrackEvent, 'livechat_openRemote').catch();
BotSagas.commandService
.remoteCall(SharedConstants.Commands.Telemetry.TrackEvent, 'bot_open', {
numOfServices: 0,
source: 'url',
})
.catch(_ => void 0);
}
BotSagas.commandService
.remoteCall(SharedConstants.Commands.Telemetry.TrackEvent, 'livechat_open', {
isDebug: action.payload.mode === 'debug',
isGov: action.payload.channelService === 'azureusgovernment',
isRemote: !isLocalHostUrl(action.payload.endpoint),
})
.catch(_ => void 0);
}
}

Expand Down
59 changes: 47 additions & 12 deletions packages/app/client/src/state/sagas/frameworkSettingsSagas.spec.ts
Expand Up @@ -30,6 +30,7 @@
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

import {
beginAdd,
editor,
Expand All @@ -40,14 +41,20 @@ import {
setDirtyFlag,
setFrameworkSettings,
FrameworkActionType,
FrameworkSettings,
SharedConstants,
} from '@bfemulator/app-shared';
import { applyMiddleware, combineReducers, createStore } from 'redux';
import sagaMiddlewareFactory from 'redux-saga';
import { put, select, takeEvery } from 'redux-saga/effects';
import { call, put, select, takeEvery } from 'redux-saga/effects';
import { CommandServiceImpl, CommandServiceInstance } from '@bfemulator/sdk-shared';

import { activeDocumentSelector, frameworkSettingsSagas, FrameworkSettingsSagas } from './frameworkSettingsSagas';
import {
activeDocumentSelector,
frameworkSettingsSagas,
FrameworkSettingsSagas,
getFrameworkSettings,
} from './frameworkSettingsSagas';

jest.mock('electron', () => ({
ipcMain: new Proxy(
Expand Down Expand Up @@ -101,31 +108,59 @@ describe('The frameworkSettingsSagas', () => {
});

it('should register the expected generators', () => {
const it = frameworkSettingsSagas();
expect(it.next().value).toEqual(
const gen = frameworkSettingsSagas();
expect(gen.next().value).toEqual(
takeEvery(FrameworkActionType.SAVE_FRAMEWORK_SETTINGS, FrameworkSettingsSagas.saveFrameworkSettings)
);
});

it('should save the framework settings', async () => {
const it = FrameworkSettingsSagas.saveFrameworkSettings(saveFrameworkSettingsAction({}));
const currentSettings: Partial<FrameworkSettings> = {
autoUpdate: false,
useCustomId: false,
usePrereleases: false,
userGUID: '',
ngrokPath: 'some/path/to/ngrok',
};
const updatedSettings: Partial<FrameworkSettings> = {
autoUpdate: true,
useCustomId: true,
usePrereleases: false,
userGUID: 'some-user-id',
ngrokPath: 'some/different/path/to/ngrok',
};
const gen = FrameworkSettingsSagas.saveFrameworkSettings(saveFrameworkSettingsAction(updatedSettings));
// selector to get the active document from the state
const selector = it.next().value;
const selector = gen.next().value;
expect(selector).toEqual(select(activeDocumentSelector));
const value = selector.SELECT.selector(mockStore.getState());
// put the dirty state to false
expect(it.next(value).value).toEqual(put(setDirtyFlag(value.documentId, false)));
expect(it.next().value).toEqual(put(setFrameworkSettings({})));
expect(it.next().done).toBe(true);
expect(gen.next(value).value).toEqual(put(setDirtyFlag(value.documentId, false)));
expect(gen.next().value).toEqual(put(setFrameworkSettings(updatedSettings)));
expect(gen.next().value).toEqual(select(getFrameworkSettings));
expect(gen.next(currentSettings).value).toEqual(
call(
[commandService, commandService.remoteCall],
SharedConstants.Commands.Telemetry.TrackEvent,
'app_changeSettings',
{
autoUpdate: true,
useCustomId: true,
userGUID: 'some-user-id',
ngrokPath: 'some/different/path/to/ngrok',
}
)
);
expect(gen.next().done).toBe(true);
});

it('should send a notification when saving the settings fails', () => {
const it = FrameworkSettingsSagas.saveFrameworkSettings(saveFrameworkSettingsAction({}));
it.next();
const gen = FrameworkSettingsSagas.saveFrameworkSettings(saveFrameworkSettingsAction({}));
gen.next();
const errMsg = `Error while saving emulator settings: oh noes!`;
const notification = newNotification(errMsg);
notification.timestamp = jasmine.any(Number) as any;
notification.id = jasmine.any(String) as any;
expect(it.throw('oh noes!').value).toEqual(put(beginAdd(notification)));
expect(gen.throw('oh noes!').value).toEqual(put(beginAdd(notification)));
});
});
20 changes: 19 additions & 1 deletion packages/app/client/src/state/sagas/frameworkSettingsSagas.ts
Expand Up @@ -40,25 +40,43 @@ import {
FrameworkAction,
FrameworkActionType,
FrameworkSettings,
SharedConstants,
} from '@bfemulator/app-shared';
import { ForkEffect, put, select, takeEvery } from 'redux-saga/effects';
import { ForkEffect, call, put, select, takeEvery } from 'redux-saga/effects';
import { CommandServiceImpl, CommandServiceInstance } from '@bfemulator/sdk-shared';

import { RootState } from '../store';
import { getSettingsDelta } from '../../utils';

export const activeDocumentSelector = (state: RootState) => {
const { editors, activeEditor } = state.editor;
const { activeDocumentId } = editors[activeEditor];
return editors[activeEditor].documents[activeDocumentId];
};

export const getFrameworkSettings = (state: RootState): FrameworkSettings => state.framework;

export class FrameworkSettingsSagas {
@CommandServiceInstance()
private static commandService: CommandServiceImpl;

// when saving settings from the settings editor we need to mark the document as clean
// and then set the settings
public static *saveFrameworkSettings(action: FrameworkAction<FrameworkSettings>): IterableIterator<any> {
try {
const activeDoc: Document = yield select(activeDocumentSelector);
yield put(setDirtyFlag(activeDoc.documentId, false)); // mark as clean
yield put(setFrameworkSettings(action.payload));
const currentSettings = yield select(getFrameworkSettings);
const settingsDelta = getSettingsDelta(currentSettings, action.payload);
if (settingsDelta) {
yield call(
[FrameworkSettingsSagas.commandService, FrameworkSettingsSagas.commandService.remoteCall],
SharedConstants.Commands.Telemetry.TrackEvent,
'app_changeSettings',
settingsDelta
);
}
} catch (e) {
const errMsg = `Error while saving emulator settings: ${e}`;
const notification = newNotification(errMsg);
Expand Down
70 changes: 70 additions & 0 deletions packages/app/client/src/utils/getSettingsDelta.spec.ts
@@ -0,0 +1,70 @@
//
// Bot Framework Emulator Github:
// https://github.com/Microsoft/BotFramwork-Emulator
//
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

import { FrameworkSettings } from '@bfemulator/app-shared';

import { getSettingsDelta } from './getSettingsDelta';

describe('getSettingsDelta', () => {
it('should return an object containing the delta between 2 settings objects', () => {
const currentSettings: Partial<FrameworkSettings> = {
autoUpdate: true,
locale: 'en-us',
use10Tokens: true,
usePrereleases: true,
userGUID: 'some-id',
};
const updatedSettings: Partial<FrameworkSettings> = {
autoUpdate: true,
runNgrokAtStartup: true,
use10Tokens: false,
usePrereleases: false,
userGUID: 'some-other-id',
};

expect(getSettingsDelta(currentSettings, updatedSettings)).toEqual({
locale: undefined,
runNgrokAtStartup: true,
use10Tokens: false,
usePrereleases: false,
userGUID: 'some-other-id',
});
});

it('should return undefined for settings objects that do not contain a delta', () => {
const currentSettings: Partial<FrameworkSettings> = {
autoUpdate: true,
use10Tokens: true,
usePrereleases: true,
userGUID: 'some-id',
};
const updatedSettings = currentSettings;

expect(getSettingsDelta(currentSettings, updatedSettings)).toBe(undefined);
});
});
56 changes: 56 additions & 0 deletions packages/app/client/src/utils/getSettingsDelta.ts
@@ -0,0 +1,56 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license.
//
// Microsoft Bot Framework: http://botframework.com
//
// Bot Framework Emulator Github:
// https://github.com/Microsoft/BotFramwork-Emulator
//
// Copyright (c) Microsoft Corporation
// All rights reserved.
//
// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

import { FrameworkSettings } from '@bfemulator/app-shared';

export function getSettingsDelta(
prevSettings: FrameworkSettings,
updatedSettings: FrameworkSettings
): Partial<FrameworkSettings> {
const delta: Partial<FrameworkSettings> = {};
// get delta for keys present in updated settings
for (const key in updatedSettings) {
const prevVal = prevSettings[key];
const updatedVal = updatedSettings[key];
if (prevVal !== updatedVal) {
delta[key] = updatedVal;
}
}
// get delta for any keys that were deleted from updated settings
for (const key in prevSettings) {
if (!Object.prototype.hasOwnProperty.call(updatedSettings, key)) {
delta[key] = undefined;
}
}
return Object.keys(delta).length ? delta : undefined;
}
1 change: 1 addition & 0 deletions packages/app/client/src/utils/index.ts
Expand Up @@ -34,5 +34,6 @@
export * from './debounce';
export * from './expandFlatTree';
export * from './getGlobal';
export * from './getSettingsDelta';
export * from './generateBotSecret';
export * from './chatUtils';
2 changes: 2 additions & 0 deletions packages/app/main/src/commands/ngrokCommands.ts
Expand Up @@ -36,6 +36,7 @@ import { Command } from '@bfemulator/sdk-shared';

import { store } from '../state/store';
import { Emulator } from '../emulator';
import { TelemetryService } from '../telemetry';

const Commands = SharedConstants.Commands.Ngrok;

Expand All @@ -48,6 +49,7 @@ export class NgrokCommands {
try {
await emulator.ngrok.recycle();
emulator.ngrok.broadcastNgrokReconnected();
TelemetryService.trackEvent('ngrok_reconnect');
} catch (e) {
throw new Error(`There was an error while trying to reconnect ngrok: ${e}`);
}
Expand Down
Expand Up @@ -35,6 +35,12 @@ import * as HttpStatus from 'http-status-codes';

import { sendTokenResponse } from './sendTokenResponse';

jest.mock('../../../../telemetry', () => ({
TelemetryService: {
trackEvent: jest.fn(),
},
}));

describe('sendTokenResponse handler', () => {
it('should send a 200 if the result of sending the token was a 200', async () => {
const req: any = {
Expand Down

0 comments on commit 399450a

Please sign in to comment.