Skip to content

Commit

Permalink
Add telemetry API and initial set of events (#2922)
Browse files Browse the repository at this point in the history
* Initial commit

* Clean up

* Add telemetry dimensions

* Fix type checking

* Add metrics

* Update doc

* Update doc

* Doc and flush

* Flush design

* Clean up

* Clean up

* Clean up

* Fix ESLint

* Clean up

* Clean up

* Add tests

* Clean up

* Add tests

* Update timeout

* Add trackException

* Clean up

* Auto send exception

* Send to useTrackException

* Add README.md

* Add entry

* Add entry

* Apply PR suggestions

* Add references

* Apply suggestions from code review

Co-Authored-By: Corina <14900841+corinagum@users.noreply.github.com>

* Apply PR suggestions

* Prettier

* Update signature

* Prettier

* Fix ESLint

* Clean up ErrorBoundary

* Update description

* Clean up

* Add tests

* Apply suggestions from code review

Co-Authored-By: TJ Durnford <tjdford@gmail.com>

* Apply PR suggestions

* Apply PR suggestions

* Refactor to useTracker

Co-authored-by: Corina <14900841+corinagum@users.noreply.github.com>
Co-authored-by: TJ Durnford <tjdford@gmail.com>
  • Loading branch information
3 people authored Feb 26, 2020
1 parent 39a3b41 commit 1ac2aaa
Show file tree
Hide file tree
Showing 29 changed files with 2,058 additions and 43 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Will be translated into 44 languages, plus 2 community-contributed translations
- For details, please read the [documentation on the localization](https://github.com/microsoft/BotFramework-WebChat/tree/master/docs/LOCALIZATION.md)
- Resolves [#2213](https://github.com/microsoft/BotFramework-WebChat/issues/2213). Added customization for typing activity, by [@compulim](https://github.com/compulim), in PR [#2912](https://github.com/microsoft/BotFramework-WebChat/pull/2912)
- Resolves [#2754](https://github.com/microsoft/BotFramework-WebChat/issues/2754). Added [telemetry system](https://github.com/microsoft/BotFramework-WebChat/tree/master/docs/TELEMETRY.md), by [@compulim](https://github.com/compulim), in PR [#2922](https://github.com/microsoft/BotFramework-WebChat/pull/2922)

### Fixed

Expand Down Expand Up @@ -142,6 +143,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Resolves [#2641](https://github.com/microsoft/BotFramework-WebChat/issues/2641). Reorganize Web Chat samples, by [@corinagum](https://github.com/corinagum), in PR [#2762](https://github.com/microsoft/BotFramework-WebChat/pull/2762)
- Resolves [#2755](https://github.com/microsoft/BotFramework-WebChat/issues/2755), added "how to use notification and customize the toast UI" sample, by [@compulim](https://github.com/compulim), in PR [#2883](https://github.com/microsoft/BotFramework-WebChat/pull/2883)
- Resolves [#2213](https://github.com/microsoft/BotFramework-WebChat/issues/2213). Added [Customize Typing Indicator Demo](https://microsoft.github.io/BotFramework-WebChat/05.custom-components/j.typing-indicator), by [@compulim](https://github.com/compulim), in PR [#2912](https://github.com/microsoft/BotFramework-WebChat/pull/2912)
- Resolves [#2754](https://github.com/microsoft/BotFramework-WebChat/issues/2754). Added [telemetry collection using Azure Application Insights](https://microsoft.github.io/BotFramework-WebChat/04.api/k.telemetry-application-insights) and [telemetry collection using Google Analytics](https://microsoft.github.io/BotFramework-WebChat/04.api/l.telemetry-google-analytics), by [@compulim](https://github.com/compulim), in PR [#2922](https://github.com/microsoft/BotFramework-WebChat/pull/2922)

## [4.7.1] - 2019-12-13

Expand Down
123 changes: 123 additions & 0 deletions __tests__/hooks/useTrackDimension.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { timeouts } from '../constants.json';

import uiConnected from '../setup/conditions/uiConnected';

jest.setTimeout(timeouts.test);

describe('useTrackDimension', () => {
let driver;
let pageObjects;

beforeEach(async () => {
const setup = await setupWebDriver({
props: {
onTelemetry: event => {
const { data, dimensions, duration, error, name, type } = event;

name !== 'init' &&
(window.WebChatTest.telemetryMeasurements || (window.WebChatTest.telemetryMeasurements = [])).push({
data,
dimensions,
duration,
error,
name,
type
});
}
}
});

driver = setup.driver;
pageObjects = setup.pageObjects;

await driver.wait(uiConnected(), timeouts.directLine);
});

test('should track string dimension', async () => {
await pageObjects.runHook('useTrackDimension', [], trackDimension => trackDimension('hello', 'aloha'));
await pageObjects.runHook('useTrackEvent', [], trackEvent => trackEvent('ping'));

await expect(driver.executeScript(() => window.WebChatTest.telemetryMeasurements.length)).resolves.toBe(1);

await pageObjects.runHook('useTrackDimension', [], trackDimension => trackDimension('hello'));
await pageObjects.runHook('useTrackEvent', [], trackEvent => trackEvent('ping2'));

await expect(driver.executeScript(() => window.WebChatTest.telemetryMeasurements)).resolves.toMatchInlineSnapshot(`
Array [
Object {
"data": null,
"dimensions": Object {
"capability:downscaleImage:workerType": "web worker",
"hello": "aloha",
"prop:locale": "en-US",
"prop:speechRecognition": "false",
"prop:speechSynthesis": "false",
},
"duration": null,
"error": null,
"name": "ping",
"type": "event",
},
Object {
"data": null,
"dimensions": Object {
"capability:downscaleImage:workerType": "web worker",
"prop:locale": "en-US",
"prop:speechRecognition": "false",
"prop:speechSynthesis": "false",
},
"duration": null,
"error": null,
"name": "ping2",
"type": "event",
},
]
`);
});

test('should not track invalid dimension name', async () => {
await pageObjects.runHook('useTrackDimension', [], trackDimension => trackDimension(123, 'hello'));
await pageObjects.runHook('useTrackEvent', [], trackEvent => trackEvent('ping'));

await expect(driver.executeScript(() => window.WebChatTest.telemetryMeasurements)).resolves.toMatchInlineSnapshot(`
Array [
Object {
"data": null,
"dimensions": Object {
"capability:downscaleImage:workerType": "web worker",
"prop:locale": "en-US",
"prop:speechRecognition": "false",
"prop:speechSynthesis": "false",
},
"duration": null,
"error": null,
"name": "ping",
"type": "event",
},
]
`);
});

test('should not track invalid dimension value', async () => {
await pageObjects.runHook('useTrackDimension', [], trackDimension => trackDimension('hello', 123));
await pageObjects.runHook('useTrackEvent', [], trackEvent => trackEvent('ping'));

await expect(driver.executeScript(() => window.WebChatTest.telemetryMeasurements)).resolves.toMatchInlineSnapshot(`
Array [
Object {
"data": null,
"dimensions": Object {
"capability:downscaleImage:workerType": "web worker",
"prop:locale": "en-US",
"prop:speechRecognition": "false",
"prop:speechSynthesis": "false",
},
"duration": null,
"error": null,
"name": "ping",
"type": "event",
},
]
`);
});
});
172 changes: 172 additions & 0 deletions __tests__/hooks/useTrackEvent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { timeouts } from '../constants.json';

import uiConnected from '../setup/conditions/uiConnected';

jest.setTimeout(timeouts.test);

describe('useTrackEvent', () => {
let driver;
let pageObjects;

beforeEach(async () => {
const setup = await setupWebDriver({
props: {
onTelemetry: event => {
const { data, dimensions, duration, error, level, name, type } = event;

name !== 'init' &&
(window.WebChatTest.telemetryMeasurements || (window.WebChatTest.telemetryMeasurements = [])).push({
data,
dimensions,
duration,
error,
level,
name,
type
});
}
}
});

driver = setup.driver;
pageObjects = setup.pageObjects;

await driver.wait(uiConnected(), timeouts.directLine);
});

test('should track simple event', async () => {
await pageObjects.runHook('useTrackEvent', [], trackEvent => trackEvent('hello'));

await expect(driver.executeScript(() => window.WebChatTest.telemetryMeasurements)).resolves.toMatchInlineSnapshot(`
Array [
Object {
"data": null,
"dimensions": Object {
"capability:downscaleImage:workerType": "web worker",
"prop:locale": "en-US",
"prop:speechRecognition": "false",
"prop:speechSynthesis": "false",
},
"duration": null,
"error": null,
"level": "info",
"name": "hello",
"type": "event",
},
]
`);
});

test('should track simple event using info explicitly', async () => {
await pageObjects.runHook('useTrackEvent', [], trackEvent => trackEvent.info('hello'));

await expect(driver.executeScript(() => window.WebChatTest.telemetryMeasurements)).resolves.toMatchInlineSnapshot(`
Array [
Object {
"data": null,
"dimensions": Object {
"capability:downscaleImage:workerType": "web worker",
"prop:locale": "en-US",
"prop:speechRecognition": "false",
"prop:speechSynthesis": "false",
},
"duration": null,
"error": null,
"level": "info",
"name": "hello",
"type": "event",
},
]
`);
});

test('should track numeric event', async () => {
await pageObjects.runHook('useTrackEvent', [], trackEvent => trackEvent.warn('hello', 123));

await expect(driver.executeScript(() => window.WebChatTest.telemetryMeasurements)).resolves.toMatchInlineSnapshot(`
Array [
Object {
"data": 123,
"dimensions": Object {
"capability:downscaleImage:workerType": "web worker",
"prop:locale": "en-US",
"prop:speechRecognition": "false",
"prop:speechSynthesis": "false",
},
"duration": null,
"error": null,
"level": "warn",
"name": "hello",
"type": "event",
},
]
`);
});

test('should track numeric event', async () => {
await pageObjects.runHook('useTrackEvent', [], trackEvent => trackEvent.debug('hello', 'aloha'));

await expect(driver.executeScript(() => window.WebChatTest.telemetryMeasurements)).resolves.toMatchInlineSnapshot(`
Array [
Object {
"data": "aloha",
"dimensions": Object {
"capability:downscaleImage:workerType": "web worker",
"prop:locale": "en-US",
"prop:speechRecognition": "false",
"prop:speechSynthesis": "false",
},
"duration": null,
"error": null,
"level": "debug",
"name": "hello",
"type": "event",
},
]
`);
});

test('should track complex event', async () => {
await pageObjects.runHook('useTrackEvent', [], trackEvent => trackEvent.error('hello', { one: 1, hello: 'aloha' }));

await expect(driver.executeScript(() => window.WebChatTest.telemetryMeasurements)).resolves.toMatchInlineSnapshot(`
Array [
Object {
"data": Object {
"hello": "aloha",
"one": 1,
},
"dimensions": Object {
"capability:downscaleImage:workerType": "web worker",
"prop:locale": "en-US",
"prop:speechRecognition": "false",
"prop:speechSynthesis": "false",
},
"duration": null,
"error": null,
"level": "error",
"name": "hello",
"type": "event",
},
]
`);
});

test('should not track event with boolean data', async () => {
await pageObjects.runHook('useTrackEvent', [], trackEvent => trackEvent('hello', true));

await expect(driver.executeScript(() => window.WebChatTest.telemetryMeasurements)).resolves.toBeFalsy();
});

test('should not track event with incompatible complex data', async () => {
await pageObjects.runHook('useTrackEvent', [], trackEvent => trackEvent('hello', { truthy: true }));

await expect(driver.executeScript(() => window.WebChatTest.telemetryMeasurements)).resolves.toBeFalsy();
});

test('should not track event with invalid name', async () => {
await pageObjects.runHook('useTrackEvent', [], trackEvent => trackEvent(123));

await expect(driver.executeScript(() => window.WebChatTest.telemetryMeasurements)).resolves.toBeFalsy();
});
});
84 changes: 84 additions & 0 deletions __tests__/hooks/useTrackException.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { timeouts } from '../constants.json';

import uiConnected from '../setup/conditions/uiConnected';

jest.setTimeout(timeouts.test);

describe('useTrackException', () => {
let driver;
let pageObjects;

beforeEach(async () => {
const setup = await setupWebDriver({
props: {
onTelemetry: event => {
const { data, dimensions, duration, error, fatal, name, type } = event;

name !== 'init' &&
(window.WebChatTest.telemetryMeasurements || (window.WebChatTest.telemetryMeasurements = [])).push({
data,
dimensions,
duration,
error: error.message,
fatal,
name,
type
});
}
}
});

driver = setup.driver;
pageObjects = setup.pageObjects;

await driver.wait(uiConnected(), timeouts.directLine);
});

test('should track exception', async () => {
await pageObjects.runHook('useTrackException', [], trackException => trackException(new Error('artificial error')));

await expect(driver.executeScript(() => window.WebChatTest.telemetryMeasurements)).resolves.toMatchInlineSnapshot(`
Array [
Object {
"data": null,
"dimensions": Object {
"capability:downscaleImage:workerType": "web worker",
"prop:locale": "en-US",
"prop:speechRecognition": "false",
"prop:speechSynthesis": "false",
},
"duration": null,
"error": "artificial error",
"fatal": true,
"name": null,
"type": "exception",
},
]
`);
});

test('should track non-fatal exception', async () => {
await pageObjects.runHook('useTrackException', [], trackException =>
trackException(new Error('non-fatal error'), false)
);

await expect(driver.executeScript(() => window.WebChatTest.telemetryMeasurements)).resolves.toMatchInlineSnapshot(`
Array [
Object {
"data": null,
"dimensions": Object {
"capability:downscaleImage:workerType": "web worker",
"prop:locale": "en-US",
"prop:speechRecognition": "false",
"prop:speechSynthesis": "false",
},
"duration": null,
"error": "non-fatal error",
"fatal": false,
"name": null,
"type": "exception",
},
]
`);
});
});
Loading

0 comments on commit 1ac2aaa

Please sign in to comment.