feature: tracking app metrics for web olny (#712)
* feature: tracking app metrics for web only
1. tracking app version with every metric
1. tracking router/page changes
1. tracking exception
* include error type and message
* uncaught exception are handled automatically by AppInsights
* caught exception are handle manually
1. tracking redux actions
* only tracking action type
Esse commit está contido em:
@@ -0,0 +1 @@
|
||||
REACT_APP_INSTRUMENTATION_KEY=40a80c0c-b913-45b7-afc9-c7eb3ed62900
|
||||
@@ -0,0 +1 @@
|
||||
REACT_APP_INSTRUMENTATION_KEY=0b9e5117-c78d-40c9-9338-921092cde49a
|
||||
@@ -1 +1,2 @@
|
||||
HOST_TYPE=electron
|
||||
INSTRUMENTATION_KEY=40a80c0c-b913-45b7-afc9-c7eb3ed62900
|
||||
|
||||
gerado
+122
-15
@@ -894,6 +894,76 @@
|
||||
"integrity": "sha1-6wlqSURtZJvcjJDwRAAwbv98M8I=",
|
||||
"dev": true
|
||||
},
|
||||
"@microsoft/applicationinsights-analytics-js": {
|
||||
"version": "2.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.0.0-beta.2.tgz",
|
||||
"integrity": "sha512-LXY3QY4Q7T6j/gCGLjmSfY9foEtkBJ318ubvdC2m8N3nQYohO63Jn4Iq8UuEzrWZjkv4u2PdInRr32H53WGysQ==",
|
||||
"requires": {
|
||||
"@microsoft/applicationinsights-common": "^2.0.0-beta.2",
|
||||
"@microsoft/applicationinsights-core-js": "^2.0.0-beta.2",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@microsoft/applicationinsights-channel-js": {
|
||||
"version": "2.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.0.0-beta.2.tgz",
|
||||
"integrity": "sha512-q33J0/a1v+c6cHsFS8oeWKD/9oAw+sZDEQLRqWnF1j7ecgE2fJ5mfAEpDMXnHCgNtlYJmwnhn2KOqxyNwFfw5w==",
|
||||
"requires": {
|
||||
"@microsoft/applicationinsights-common": "^2.0.0-beta.2",
|
||||
"@microsoft/applicationinsights-core-js": "^2.0.0-beta.2",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@microsoft/applicationinsights-common": {
|
||||
"version": "2.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-2.0.0-beta.2.tgz",
|
||||
"integrity": "sha512-kuoak6p+4v4pZcn9epPvnS/1iivpPRLbrydiEgC8gv7FnPk+kOrFZbPpfUCxJt/HZcXnRsQ6XWA2Gk2X6ZeR4Q==",
|
||||
"requires": {
|
||||
"@microsoft/applicationinsights-core-js": "^2.0.0-beta.2",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@microsoft/applicationinsights-core-js": {
|
||||
"version": "2.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.0.0-beta.2.tgz",
|
||||
"integrity": "sha512-CCuLMdyWE8YdXIk9xLCteaWYVxYbE75kGbqwUCzjC4BeWRT5SIV3ATmR/nHfCcK8tDv27oRLcYcEh/L0iJGGEA==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@microsoft/applicationinsights-dependencies-js": {
|
||||
"version": "2.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.0.0-beta.2.tgz",
|
||||
"integrity": "sha512-8tTF6znBOteV6iN+29QU3CQ61ex1FAO2OS2ucBtc31goPLM9qbpAt7vVRvxiYCurTLAyeZhhuwC+8MNF075Stw==",
|
||||
"requires": {
|
||||
"@microsoft/applicationinsights-common": "^2.0.0-beta.2",
|
||||
"@microsoft/applicationinsights-core-js": "^2.0.0-beta.2",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@microsoft/applicationinsights-properties-js": {
|
||||
"version": "2.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.0.0-beta.2.tgz",
|
||||
"integrity": "sha512-8nnzxhzTYsa+TSyHJ0BpVktFYRGHPeMnqfv4L9CYs+WRxmx1uo/tTNXqPnmNSGlJ0nuwvIdXwogGbiQZey6Dlg==",
|
||||
"requires": {
|
||||
"@microsoft/applicationinsights-common": "^2.0.0-beta.2",
|
||||
"@microsoft/applicationinsights-core-js": "^2.0.0-beta.2",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@microsoft/applicationinsights-web": {
|
||||
"version": "2.0.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web/-/applicationinsights-web-2.0.0-beta.3.tgz",
|
||||
"integrity": "sha512-D1hp+ig7fduCtfx8r2uU0/Tbbjq30JB0WS0urnPvqkhGgX6mwBu2b+XiLOweWq9kkCNJF2Wel1ToTAvxrShBxw==",
|
||||
"requires": {
|
||||
"@microsoft/applicationinsights-analytics-js": "^2.0.0-beta.2",
|
||||
"@microsoft/applicationinsights-channel-js": "^2.0.0-beta.2",
|
||||
"@microsoft/applicationinsights-common": "^2.0.0-beta.2",
|
||||
"@microsoft/applicationinsights-core-js": "^2.0.0-beta.2",
|
||||
"@microsoft/applicationinsights-dependencies-js": "^2.0.0-beta.2",
|
||||
"@microsoft/applicationinsights-properties-js": "^2.0.0-beta.2"
|
||||
}
|
||||
},
|
||||
"@mrmlnc/readdir-enhanced": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
|
||||
@@ -957,6 +1027,15 @@
|
||||
"integrity": "sha512-fOM/Jhv51iyugY7KOBZz2ThfT1gwvsGCfWxpLpZDgkGjpEO4Le9cld07OdskikLjDUQJ43dzDaVRSFwQlpdqVg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/dotenv": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-6.1.0.tgz",
|
||||
"integrity": "sha512-gmbNb7V1LbJQA4MmH0hVFgqY1cyKsa6RvKC1Xrq0WBnZ0JuuvXKciXx/s8dN0LVXCJd8xO6wIaSFSyUIoGph9g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/enzyme": {
|
||||
"version": "3.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.1.15.tgz",
|
||||
@@ -1048,9 +1127,9 @@
|
||||
}
|
||||
},
|
||||
"@types/react-router": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-4.4.3.tgz",
|
||||
"integrity": "sha1-6mi0Ahy1doZvgzZbIgFBFTdCPVA=",
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-4.4.5.tgz",
|
||||
"integrity": "sha512-12+VOu1+xiC8RPc9yrgHCyLI79VswjtuqeS2gPrMcywH6tkc8rGIUhs4LaL3AJPqo5d+RPnfRpNKiJ7MK2Qhcg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/history": "*",
|
||||
@@ -1146,15 +1225,6 @@
|
||||
"redux": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@types/redux-thunk": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "http://registry.npmjs.org/@types/redux-thunk/-/redux-thunk-2.1.0.tgz",
|
||||
"integrity": "sha1-vCtulylhgxr7gqm/TwZybjUflBY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"redux-thunk": "*"
|
||||
}
|
||||
},
|
||||
"@types/snapsvg": {
|
||||
"version": "0.4.35",
|
||||
"resolved": "https://registry.npmjs.org/@types/snapsvg/-/snapsvg-0.4.35.tgz",
|
||||
@@ -5019,9 +5089,9 @@
|
||||
}
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.0.0.tgz",
|
||||
"integrity": "sha512-FlWbnhgjtwD+uNLUGHbMykMOYQaTivdHEmYwAKFjn6GKe/CqY0fNae93ZHTd20snh9ZLr8mTzIL9m0APQ1pjQg=="
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz",
|
||||
"integrity": "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g=="
|
||||
},
|
||||
"dotenv-expand": {
|
||||
"version": "4.2.0",
|
||||
@@ -14193,6 +14263,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-appinsights": {
|
||||
"version": "3.0.0-rc.5",
|
||||
"resolved": "https://registry.npmjs.org/react-appinsights/-/react-appinsights-3.0.0-rc.5.tgz",
|
||||
"integrity": "sha512-su36gr+PsMAUq60DSP8GBmIXm65W/U2f1tpPNsKhfTZ0unT1fVhNPf9zYemSPaOHlNbn/2B/vhYfCuPT1NfsGg==",
|
||||
"requires": {
|
||||
"@microsoft/applicationinsights-web": "^2.0.0-beta.3",
|
||||
"history": "^4.7.2",
|
||||
"react": "^16.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": {
|
||||
"version": "16.8.4",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.8.4.tgz",
|
||||
"integrity": "sha512-0GQ6gFXfUH7aZcjGVymlPOASTuSjlQL4ZtVC5YKH+3JL6bBLCVO21DknzmaPlI90LN253ojj02nsapy+j7wIjg==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"scheduler": {
|
||||
"version": "0.13.4",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.4.tgz",
|
||||
"integrity": "sha512-cvSOlRPxOHs5dAhP9yiS/6IDmVAVxmk33f0CtTJRkmUWcb1Us+t7b1wqdzoC0REw2muC9V5f1L/w5R5uKGaepA==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-dev-utils": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-6.1.1.tgz",
|
||||
@@ -14627,6 +14729,11 @@
|
||||
"workbox-webpack-plugin": "3.6.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.0.0.tgz",
|
||||
"integrity": "sha512-FlWbnhgjtwD+uNLUGHbMykMOYQaTivdHEmYwAKFjn6GKe/CqY0fNae93ZHTd20snh9ZLr8mTzIL9m0APQ1pjQg=="
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.0.tgz",
|
||||
|
||||
+3
-1
@@ -21,12 +21,14 @@
|
||||
"bootstrap": "^4.1.3",
|
||||
"buffer-reverse": "^1.0.1",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"dotenv": "^7.0.0",
|
||||
"google-protobuf": "^3.6.1",
|
||||
"lodash": "^4.17.11",
|
||||
"md5.js": "^1.3.5",
|
||||
"node-int64": "^0.4.0",
|
||||
"rc-menu": "^7.4.21",
|
||||
"react": "^16.7.0",
|
||||
"react-appinsights": "^3.0.0-rc.5",
|
||||
"react-dom": "^16.7.0",
|
||||
"react-jsonschema-form": "^1.3.0",
|
||||
"react-localization": "^1.0.13",
|
||||
@@ -85,6 +87,7 @@
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.5.0",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/dotenv": "^6.1.0",
|
||||
"@types/enzyme": "^3.1.15",
|
||||
"@types/jest": "23.3.9",
|
||||
"@types/node": "10.12.7",
|
||||
@@ -97,7 +100,6 @@
|
||||
"@types/reactstrap": "^6.4.3",
|
||||
"@types/redux-logger": "^3.0.6",
|
||||
"@types/redux-mock-store": "^1.0.0",
|
||||
"@types/redux-thunk": "^2.1.0",
|
||||
"cross-env": "^5.2.0",
|
||||
"electron": "^3.0.13",
|
||||
"electron-builder": "^20.38.3",
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ import createReduxStore from "./redux/store/store";
|
||||
import initialState from "./redux/store/initialState";
|
||||
import { IApplicationState } from "./models//applicationState";
|
||||
import { mount } from "enzyme";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
import { Router } from "react-router-dom";
|
||||
import { KeyboardManager } from "./react/components/common/keyboardManager/keyboardManager";
|
||||
import { ErrorHandler } from "./react/components/common/errorHandler/errorHandler";
|
||||
|
||||
@@ -27,7 +27,7 @@ describe("App Component", () => {
|
||||
|
||||
it("renders required top level components", () => {
|
||||
const wrapper = createComponent();
|
||||
expect(wrapper.find(HashRouter).exists()).toBe(true);
|
||||
expect(wrapper.find(Router).exists()).toBe(true);
|
||||
expect(wrapper.find(KeyboardManager).exists()).toEqual(true);
|
||||
expect(wrapper.find(ErrorHandler).exists()).toEqual(true);
|
||||
});
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
import React, { Fragment } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { HashRouter as Router, NavLink, Link } from "react-router-dom";
|
||||
import { Router } from "react-router-dom";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import Sidebar from "./react/components/shell/sidebar";
|
||||
import MainContentRouter from "./react/components/shell/mainContentRouter";
|
||||
@@ -15,6 +15,7 @@ import { TitleBar } from "./react/components/shell/titleBar";
|
||||
import { StatusBar } from "./react/components/shell/statusBar";
|
||||
import { StatusBarMetrics } from "./react/components/shell/statusBarMetrics";
|
||||
import { HelpMenu } from "./react/components/shell/helpMenu";
|
||||
import history from "./history";
|
||||
|
||||
interface IAppProps {
|
||||
currentProject?: IProject;
|
||||
@@ -73,7 +74,7 @@ export default class App extends React.Component<IAppProps> {
|
||||
{/* Don't render app contents during a render error */}
|
||||
{(!this.props.appError || this.props.appError.errorCode !== ErrorCode.GenericRenderError) &&
|
||||
<KeyboardManager>
|
||||
<Router>
|
||||
<Router history={history}>
|
||||
<div className={`app-shell platform-${platform}`}>
|
||||
<TitleBar icon="fas fa-tags"
|
||||
title={this.props.currentProject ? this.props.currentProject.name : ""}>
|
||||
|
||||
@@ -43,4 +43,8 @@ function getHostProcess(): IHostProcess {
|
||||
};
|
||||
}
|
||||
|
||||
export function isElectron(): boolean {
|
||||
return getHostProcess().type === HostProcessType.Electron;
|
||||
}
|
||||
|
||||
export default getHostProcess;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
import { createHashHistory } from "history";
|
||||
export default createHashHistory();
|
||||
+5
-1
@@ -13,6 +13,10 @@ import { IApplicationState } from "./models/applicationState";
|
||||
import registerProviders from "./registerProviders";
|
||||
import registerMixins from "./registerMixins";
|
||||
|
||||
import { setUpAppInsights } from "./telemetry";
|
||||
|
||||
setUpAppInsights();
|
||||
|
||||
registerMixins();
|
||||
registerProviders();
|
||||
const defaultState: IApplicationState = initialState;
|
||||
@@ -20,7 +24,7 @@ const store = createReduxStore(defaultState, true);
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
<App/>
|
||||
</Provider>
|
||||
, document.getElementById("root"));
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { IAppError, ErrorCode, AppError } from "../../../../models/applicationSt
|
||||
import { strings } from "../../../../common/strings";
|
||||
import Alert from "../alert/alert";
|
||||
import { Env } from "../../../../common/environment";
|
||||
import { trackError } from "../../../../telemetry";
|
||||
|
||||
/**
|
||||
* Component properties for ErrorHandler component
|
||||
@@ -120,6 +121,10 @@ export class ErrorHandler extends React.Component<IErrorHandlerProps> {
|
||||
message: this.getUnknownErrorMessage(error),
|
||||
};
|
||||
}
|
||||
|
||||
// appInsights: track error event
|
||||
trackError(appError);
|
||||
|
||||
this.props.onError(appError);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { trackReduxAction } from "../../telemetry";
|
||||
import { createAppInsightsLogger } from "./appInsights";
|
||||
jest.mock("../../telemetry");
|
||||
|
||||
describe("appInsights middleware", () => {
|
||||
const create = () => {
|
||||
const appInsightsLogger = createAppInsightsLogger();
|
||||
|
||||
const store = {
|
||||
getState: jest.fn(() => ({})),
|
||||
dispatch: jest.fn(),
|
||||
};
|
||||
|
||||
const next = jest.fn();
|
||||
const invoke = (action) => appInsightsLogger(store)(next)(action);
|
||||
|
||||
return { store, next, invoke};
|
||||
};
|
||||
|
||||
it("calls trackReduxAction", () => {
|
||||
const { invoke } = create();
|
||||
const action = { type: "TEST"};
|
||||
invoke(action);
|
||||
|
||||
expect(trackReduxAction).toHaveBeenCalledWith(action);
|
||||
});
|
||||
|
||||
it("passes through non-function action", () => {
|
||||
const { next, invoke } = create();
|
||||
const action = { type: "TEST" };
|
||||
invoke(action);
|
||||
expect(next).toHaveBeenCalledWith(action);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { AnyAction, Dispatch, Middleware, MiddlewareAPI } from "redux";
|
||||
import { trackReduxAction } from "../../telemetry";
|
||||
|
||||
/**
|
||||
* return a middleware that send custom event to AppInsights tracking redux action
|
||||
*/
|
||||
export function createAppInsightsLogger(): Middleware {
|
||||
return (store: MiddlewareAPI<Dispatch<AnyAction>>) => (next: Dispatch<AnyAction>) => (action: any) => {
|
||||
trackReduxAction(action);
|
||||
return next(action);
|
||||
};
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import thunk from "redux-thunk";
|
||||
import rootReducer from "../reducers";
|
||||
import { IApplicationState } from "../../models/applicationState";
|
||||
import { mergeInitialState } from "../middleware/localStorage";
|
||||
import { createAppInsightsLogger } from "../middleware/appInsights";
|
||||
import { Env } from "../../common/environment";
|
||||
|
||||
/**
|
||||
@@ -15,7 +16,7 @@ export default function createReduxStore(
|
||||
useLocalStorage: boolean = false): Store {
|
||||
const paths: string[] = ["appSettings", "connections", "recentProjects"];
|
||||
|
||||
let middlewares = [thunk];
|
||||
let middlewares = [thunk, createAppInsightsLogger()];
|
||||
|
||||
if (useLocalStorage) {
|
||||
const localStorage = require("../middleware/localStorage");
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { setUpAppInsights, trackError, trackReduxAction } from "./telemetry";
|
||||
import { ApplicationInsights, SeverityLevel, IExceptionTelemetry } from "@microsoft/applicationinsights-web";
|
||||
import { Action } from "redux";
|
||||
import { ErrorCode } from "./models/applicationState";
|
||||
import { isElectron } from "./common/hostProcess";
|
||||
|
||||
jest.mock("./common/hostProcess");
|
||||
jest.mock("@microsoft/applicationinsights-web");
|
||||
|
||||
describe("appInsights telemetry", () => {
|
||||
const isElectronMock = isElectron as jest.Mock;
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("browser mode", () => {
|
||||
beforeEach(() => {
|
||||
isElectronMock.mockImplementation(() => false);
|
||||
});
|
||||
|
||||
it("setUpAppInsights load an appInsights object", () => {
|
||||
const spy = jest.spyOn(ApplicationInsights.prototype, "loadAppInsights");
|
||||
setUpAppInsights();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("trackReduxAction call trackEvent", () => {
|
||||
const spy = jest.spyOn(ApplicationInsights.prototype, "trackEvent");
|
||||
setUpAppInsights();
|
||||
|
||||
const action: Action = {type: "test"};
|
||||
trackReduxAction(action);
|
||||
|
||||
expect(spy).toBeCalledWith({
|
||||
name: "test",
|
||||
});
|
||||
});
|
||||
|
||||
it("trackError call trackException", () => {
|
||||
const spy = jest.spyOn(ApplicationInsights.prototype, "trackException");
|
||||
setUpAppInsights();
|
||||
|
||||
const appError = {
|
||||
errorCode: ErrorCode.Unknown,
|
||||
message: "test message",
|
||||
};
|
||||
trackError(appError);
|
||||
|
||||
const expectedExceptionTelemetry: IExceptionTelemetry = {
|
||||
error: new Error(ErrorCode.Unknown),
|
||||
properties: {
|
||||
message: appError.message,
|
||||
},
|
||||
severityLevel: SeverityLevel.Error,
|
||||
};
|
||||
|
||||
expect(spy).toBeCalledWith(expectedExceptionTelemetry);
|
||||
});
|
||||
});
|
||||
|
||||
describe("electron mode", () => {
|
||||
beforeEach(() => {
|
||||
isElectronMock.mockImplementation(() => true);
|
||||
});
|
||||
|
||||
it("setUpAppInsights should do nothing", () => {
|
||||
const spy = jest.spyOn(ApplicationInsights.prototype, "loadAppInsights");
|
||||
setUpAppInsights();
|
||||
expect(spy).not.toBeCalled();
|
||||
});
|
||||
|
||||
it("trackError does not call trackException", () => {
|
||||
const spy = jest.spyOn(ApplicationInsights.prototype, "trackException");
|
||||
setUpAppInsights();
|
||||
|
||||
const appError = {
|
||||
errorCode: ErrorCode.Unknown,
|
||||
message: "test message",
|
||||
};
|
||||
trackError(appError);
|
||||
expect(spy).not.toBeCalled();
|
||||
});
|
||||
|
||||
it("trackReduxAction does not call trackEvent", () => {
|
||||
const spy = jest.spyOn(ApplicationInsights.prototype, "trackEvent");
|
||||
setUpAppInsights();
|
||||
|
||||
const action: Action = {type: "test"};
|
||||
trackReduxAction(action);
|
||||
expect(spy).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
import { Env } from "./common/environment";
|
||||
import { reactAI } from "react-appinsights";
|
||||
import history from "./history";
|
||||
import { ApplicationInsights, IExceptionTelemetry, SeverityLevel } from "@microsoft/applicationinsights-web";
|
||||
import { version } from "../package.json";
|
||||
import { isElectron } from "./common/hostProcess";
|
||||
import { Action } from "redux";
|
||||
import { IAppError } from "./models/applicationState";
|
||||
import { config } from "dotenv";
|
||||
|
||||
// vott-app-insights
|
||||
config();
|
||||
const instrumentationKey = process.env.REACT_APP_INSTRUMENTATION_KEY;
|
||||
|
||||
let debug = false;
|
||||
let maxBatchSize = 250;
|
||||
|
||||
if (Env.get() !== "production") {
|
||||
// for development/testing
|
||||
// myho-appinsights
|
||||
debug = true;
|
||||
maxBatchSize = 0; // send telemetry as soon as it's collected
|
||||
}
|
||||
|
||||
let appInsights;
|
||||
|
||||
/**
|
||||
* create an app insights connection for web version
|
||||
* do nothing for electron mode
|
||||
*/
|
||||
export function setUpAppInsights() {
|
||||
if (isElectron()) {
|
||||
return;
|
||||
}
|
||||
|
||||
reactAI.setContext({
|
||||
AppVersion: version,
|
||||
});
|
||||
|
||||
const config = {
|
||||
instrumentationKey,
|
||||
maxBatchSize,
|
||||
extensions: [reactAI],
|
||||
extensionConfig: {
|
||||
[reactAI.extensionId]: {
|
||||
debug,
|
||||
history, // required for tracking router changes
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
appInsights = new ApplicationInsights({config});
|
||||
appInsights.loadAppInsights();
|
||||
}
|
||||
|
||||
/**
|
||||
* send exception event to AppInsights
|
||||
* @param appError object containing the error type and error message
|
||||
*/
|
||||
export function trackError(appError: IAppError): void {
|
||||
if (isElectron()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const error = new Error(appError.errorCode);
|
||||
const exceptionTelemetry: IExceptionTelemetry = {
|
||||
error,
|
||||
properties: {
|
||||
message: appError.message,
|
||||
},
|
||||
severityLevel: SeverityLevel.Error,
|
||||
};
|
||||
|
||||
appInsights.trackException(exceptionTelemetry);
|
||||
}
|
||||
|
||||
/**
|
||||
* send custom event tracking redux action
|
||||
* @param action a redux action
|
||||
*/
|
||||
export function trackReduxAction(action: Action): void {
|
||||
if (isElectron()) {
|
||||
return;
|
||||
}
|
||||
|
||||
appInsights.trackEvent({
|
||||
name: action.type,
|
||||
});
|
||||
}
|
||||
Referência em uma Nova Issue
Bloquear um usuário