Skip to content

Commit

Permalink
Added new telemetry events and properties.
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyanziano committed Jan 27, 2020
1 parent 16845ae commit 4666dc4
Show file tree
Hide file tree
Showing 15 changed files with 218 additions and 21 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 Debugger UI 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)

## Changed
- [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 @@ -73,10 +73,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
10 changes: 7 additions & 3 deletions packages/app/client/src/state/sagas/botSagas.ts
Expand Up @@ -167,9 +167,13 @@ export class BotSagas {
source: 'url',
});
}
if (!isLocalHostUrl(action.payload.endpoint)) {
BotSagas.commandService.remoteCall(SharedConstants.Commands.Telemetry.TrackEvent, 'livechat_openRemote').catch();
}
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();
}
}

Expand Down
41 changes: 37 additions & 4 deletions packages/app/client/src/state/sagas/frameworkSettingsSagas.spec.ts
Expand Up @@ -30,7 +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 { newNotification, SharedConstants } from '@bfemulator/app-shared';
import { newNotification, SharedConstants, FrameworkSettings } from '@bfemulator/app-shared';
import { applyMiddleware, combineReducers, createStore } from 'redux';
import sagaMiddlewareFactory from 'redux-saga';
import { call, put, select, takeEvery } from 'redux-saga/effects';
Expand All @@ -47,7 +47,12 @@ import { beginAdd } from '../actions/notificationActions';
import { editor } from '../reducers/editor';
import { framework } from '../reducers/framework';

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

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

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 it = FrameworkSettingsSagas.saveFrameworkSettings(saveFrameworkSettingsAction(updatedSettings));
// selector to get the active document from the state
const selector = it.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(EditorActions.setDirtyFlag(value.documentId, false)));
expect(it.next().value).toEqual(put(setFrameworkSettings({})));
expect(it.next().value).toEqual(put(setFrameworkSettings(updatedSettings)));
expect(it.next().value).toEqual(select(getFrameworkSettings));
expect(it.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(it.next().done).toBe(true);
});

Expand Down
21 changes: 19 additions & 2 deletions packages/app/client/src/state/sagas/frameworkSettingsSagas.ts
Expand Up @@ -30,29 +30,46 @@
// 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 { newNotification, FrameworkSettings } from '@bfemulator/app-shared';
import { ForkEffect, put, select, takeEvery } from 'redux-saga/effects';
import { newNotification, FrameworkSettings, SharedConstants } from '@bfemulator/app-shared';
import { ForkEffect, call, put, select, takeEvery } from 'redux-saga/effects';
import { CommandServiceImpl, CommandServiceInstance } from '@bfemulator/sdk-shared';

import * as EditorActions from '../actions/editorActions';
import { FrameworkAction, FrameworkActionType, setFrameworkSettings } from '../actions/frameworkSettingsActions';
import { beginAdd } from '../actions/notificationActions';
import { Document } from '../reducers/editor';
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(EditorActions.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
68 changes: 68 additions & 0 deletions packages/app/client/src/utils/getSettingsDelta.spec.ts
@@ -0,0 +1,68 @@
//
// 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,
use10Tokens: true,
usePrereleases: true,
userGUID: 'some-id',
};
const updatedSettings: Partial<FrameworkSettings> = {
autoUpdate: true,
use10Tokens: false,
usePrereleases: false,
userGUID: 'some-other-id',
runNgrokAtStartup: true,
};

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

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);
});
});
49 changes: 49 additions & 0 deletions packages/app/client/src/utils/getSettingsDelta.ts
@@ -0,0 +1,49 @@
//
// 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> = {};
for (const key in updatedSettings) {
const prevVal = prevSettings[key];
const updatedVal = updatedSettings[key];
if (prevVal !== updatedVal) {
delta[key] = updatedVal;
}
}
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 @@ -35,6 +35,7 @@ import { SharedConstants } from '@bfemulator/app-shared';
import { Command } from '@bfemulator/sdk-shared';

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
Expand Up @@ -34,6 +34,8 @@
import * as HttpStatus from 'http-status-codes';
import { Next, Response } from 'restify';

import { TelemetryService } from '../../../../telemetry';

import { ConversationAwareRequest } from './getConversation';

export async function sendTokenResponse(req: ConversationAwareRequest, res: Response, next: Next): Promise<any> {
Expand All @@ -46,6 +48,7 @@ export async function sendTokenResponse(req: ConversationAwareRequest, res: Resp

if (statusCode === HttpStatus.OK) {
res.send(HttpStatus.OK, body);
TelemetryService.trackEvent('oauth_sendToken');
} else {
res.send(statusCode);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/app/main/src/state/sagas/settingsSagas.ts
Expand Up @@ -62,7 +62,7 @@ export class SettingsSagas {
public static *setFramework(action: FrameworkAction<FrameworkSettings>): IterableIterator<any> {
const emulator = Emulator.getInstance();
yield emulator.ngrok.updateNgrokFromSettings(action.payload);
//emulator.framework.server.botEmulator.facilities.locale = action.payload.locale;
emulator.server.state.locale = action.payload.locale;
yield* SettingsSagas.pushClientAwareSettings();
}

Expand Down

0 comments on commit 4666dc4

Please sign in to comment.