Skip to content

Commit 74f5237

Browse files
mydiemhowbreza
authored andcommitted
feature: tracking app metrics for web olny (microsoft#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
1 parent d151292 commit 74f5237

16 files changed

+381
-22
lines changed

.env.development

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
REACT_APP_INSTRUMENTATION_KEY=40a80c0c-b913-45b7-afc9-c7eb3ed62900

.env.production

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
REACT_APP_INSTRUMENTATION_KEY=0b9e5117-c78d-40c9-9338-921092cde49a

.env.test

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
HOST_TYPE=electron
2+
INSTRUMENTATION_KEY=40a80c0c-b913-45b7-afc9-c7eb3ed62900

package-lock.json

+122-15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
"bootstrap": "^4.1.3",
2222
"buffer-reverse": "^1.0.1",
2323
"crypto-js": "^3.1.9-1",
24+
"dotenv": "^7.0.0",
2425
"google-protobuf": "^3.6.1",
2526
"lodash": "^4.17.11",
2627
"md5.js": "^1.3.5",
2728
"node-int64": "^0.4.0",
2829
"rc-menu": "^7.4.21",
2930
"react": "^16.7.0",
31+
"react-appinsights": "^3.0.0-rc.5",
3032
"react-dom": "^16.7.0",
3133
"react-jsonschema-form": "^1.3.0",
3234
"react-localization": "^1.0.13",
@@ -85,6 +87,7 @@
8587
"devDependencies": {
8688
"@fortawesome/fontawesome-free": "^5.5.0",
8789
"@types/axios": "^0.14.0",
90+
"@types/dotenv": "^6.1.0",
8891
"@types/enzyme": "^3.1.15",
8992
"@types/jest": "23.3.9",
9093
"@types/node": "10.12.7",
@@ -97,7 +100,6 @@
97100
"@types/reactstrap": "^6.4.3",
98101
"@types/redux-logger": "^3.0.6",
99102
"@types/redux-mock-store": "^1.0.0",
100-
"@types/redux-thunk": "^2.1.0",
101103
"cross-env": "^5.2.0",
102104
"electron": "^3.0.13",
103105
"electron-builder": "^20.38.3",

src/App.test.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import createReduxStore from "./redux/store/store";
55
import initialState from "./redux/store/initialState";
66
import { IApplicationState } from "./models//applicationState";
77
import { mount } from "enzyme";
8-
import { HashRouter } from "react-router-dom";
8+
import { Router } from "react-router-dom";
99
import { KeyboardManager } from "./react/components/common/keyboardManager/keyboardManager";
1010
import { ErrorHandler } from "./react/components/common/errorHandler/errorHandler";
1111

@@ -27,7 +27,7 @@ describe("App Component", () => {
2727

2828
it("renders required top level components", () => {
2929
const wrapper = createComponent();
30-
expect(wrapper.find(HashRouter).exists()).toBe(true);
30+
expect(wrapper.find(Router).exists()).toBe(true);
3131
expect(wrapper.find(KeyboardManager).exists()).toEqual(true);
3232
expect(wrapper.find(ErrorHandler).exists()).toEqual(true);
3333
});

src/App.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { Fragment } from "react";
22
import { connect } from "react-redux";
3-
import { HashRouter as Router, NavLink, Link } from "react-router-dom";
3+
import { Router } from "react-router-dom";
44
import { ToastContainer } from "react-toastify";
55
import Sidebar from "./react/components/shell/sidebar";
66
import MainContentRouter from "./react/components/shell/mainContentRouter";
@@ -15,6 +15,7 @@ import { TitleBar } from "./react/components/shell/titleBar";
1515
import { StatusBar } from "./react/components/shell/statusBar";
1616
import { StatusBarMetrics } from "./react/components/shell/statusBarMetrics";
1717
import { HelpMenu } from "./react/components/shell/helpMenu";
18+
import history from "./history";
1819

1920
interface IAppProps {
2021
currentProject?: IProject;
@@ -73,7 +74,7 @@ export default class App extends React.Component<IAppProps> {
7374
{/* Don't render app contents during a render error */}
7475
{(!this.props.appError || this.props.appError.errorCode !== ErrorCode.GenericRenderError) &&
7576
<KeyboardManager>
76-
<Router>
77+
<Router history={history}>
7778
<div className={`app-shell platform-${platform}`}>
7879
<TitleBar icon="fas fa-tags"
7980
title={this.props.currentProject ? this.props.currentProject.name : ""}>

src/common/hostProcess.ts

+4
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,8 @@ function getHostProcess(): IHostProcess {
4343
};
4444
}
4545

46+
export function isElectron(): boolean {
47+
return getHostProcess().type === HostProcessType.Electron;
48+
}
49+
4650
export default getHostProcess;

src/history.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import { createHashHistory } from "history";
2+
export default createHashHistory();

src/index.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,18 @@ import { IApplicationState } from "./models/applicationState";
1313
import registerProviders from "./registerProviders";
1414
import registerMixins from "./registerMixins";
1515

16+
import { setUpAppInsights } from "./telemetry";
17+
18+
setUpAppInsights();
19+
1620
registerMixins();
1721
registerProviders();
1822
const defaultState: IApplicationState = initialState;
1923
const store = createReduxStore(defaultState, true);
2024

2125
ReactDOM.render(
2226
<Provider store={store}>
23-
<App />
27+
<App/>
2428
</Provider>
2529
, document.getElementById("root"));
2630

src/react/components/common/errorHandler/errorHandler.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { IAppError, ErrorCode, AppError } from "../../../../models/applicationSt
33
import { strings } from "../../../../common/strings";
44
import Alert from "../alert/alert";
55
import { Env } from "../../../../common/environment";
6+
import { trackError } from "../../../../telemetry";
67

78
/**
89
* Component properties for ErrorHandler component
@@ -120,6 +121,10 @@ export class ErrorHandler extends React.Component<IErrorHandlerProps> {
120121
message: this.getUnknownErrorMessage(error),
121122
};
122123
}
124+
125+
// appInsights: track error event
126+
trackError(appError);
127+
123128
this.props.onError(appError);
124129
}
125130

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { trackReduxAction } from "../../telemetry";
2+
import { createAppInsightsLogger } from "./appInsights";
3+
jest.mock("../../telemetry");
4+
5+
describe("appInsights middleware", () => {
6+
const create = () => {
7+
const appInsightsLogger = createAppInsightsLogger();
8+
9+
const store = {
10+
getState: jest.fn(() => ({})),
11+
dispatch: jest.fn(),
12+
};
13+
14+
const next = jest.fn();
15+
const invoke = (action) => appInsightsLogger(store)(next)(action);
16+
17+
return { store, next, invoke};
18+
};
19+
20+
it("calls trackReduxAction", () => {
21+
const { invoke } = create();
22+
const action = { type: "TEST"};
23+
invoke(action);
24+
25+
expect(trackReduxAction).toHaveBeenCalledWith(action);
26+
});
27+
28+
it("passes through non-function action", () => {
29+
const { next, invoke } = create();
30+
const action = { type: "TEST" };
31+
invoke(action);
32+
expect(next).toHaveBeenCalledWith(action);
33+
});
34+
});

src/redux/middleware/appInsights.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { AnyAction, Dispatch, Middleware, MiddlewareAPI } from "redux";
2+
import { trackReduxAction } from "../../telemetry";
3+
4+
/**
5+
* return a middleware that send custom event to AppInsights tracking redux action
6+
*/
7+
export function createAppInsightsLogger(): Middleware {
8+
return (store: MiddlewareAPI<Dispatch<AnyAction>>) => (next: Dispatch<AnyAction>) => (action: any) => {
9+
trackReduxAction(action);
10+
return next(action);
11+
};
12+
}

src/redux/store/store.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import thunk from "redux-thunk";
33
import rootReducer from "../reducers";
44
import { IApplicationState } from "../../models/applicationState";
55
import { mergeInitialState } from "../middleware/localStorage";
6+
import { createAppInsightsLogger } from "../middleware/appInsights";
67
import { Env } from "../../common/environment";
78

89
/**
@@ -15,7 +16,7 @@ export default function createReduxStore(
1516
useLocalStorage: boolean = false): Store {
1617
const paths: string[] = ["appSettings", "connections", "recentProjects"];
1718

18-
let middlewares = [thunk];
19+
let middlewares = [thunk, createAppInsightsLogger()];
1920

2021
if (useLocalStorage) {
2122
const localStorage = require("../middleware/localStorage");

0 commit comments

Comments
 (0)