Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introducing React Hooks for AppInsights #1120

Merged
merged 25 commits into from May 29, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b21b0df
Bumping React types to support hooks
aaronpowell Nov 19, 2019
9865f6e
Added a context object for the React plugin
aaronpowell Nov 19, 2019
0c68d6e
Adding trackEvent to ReactPlugin
aaronpowell Nov 19, 2019
966526b
Adding custom hooks to track an event and metric and exported from pa…
aaronpowell Nov 19, 2019
076a77f
Merge branch 'aaronpowell/issue-991' of https://github.com/aaronpowel…
markwolff May 19, 2020
c2144e5
Merge branch 'master' of github.com:microsoft/ApplicationInsights-JS …
markwolff May 27, 2020
123c0df
fix: package.json typo
markwolff May 27, 2020
062160d
remove react from gruntfile
markwolff May 27, 2020
5d5eb14
Merge branch 'master' into aaronpowell/issue-991
May 27, 2020
9b9283a
Merge branch 'aaronpowell/issue-991' into update-hooks-pr
markwolff May 27, 2020
9bcd6b2
Merge pull request #1 from markwolff/update-hooks-pr
aaronpowell May 27, 2020
de34258
fix logger
markwolff May 28, 2020
e4dd2be
Merge branch 'aaronpowell/issue-991' of github.com:aaronpowell/Applic…
markwolff May 28, 2020
da47bbc
remove dead logger
markwolff May 28, 2020
a2a59ee
remove react from rush
markwolff May 28, 2020
617ed40
remove react from shrinkwrap
markwolff May 28, 2020
cbac445
revert lockfile commit
markwolff May 28, 2020
54b5829
Merge branch 'master' into aaronpowell/issue-991
May 28, 2020
3a3599d
use correct working directory
markwolff May 28, 2020
b34b66c
Merge branch 'aaronpowell/issue-991' of github.com:aaronpowell/Applic…
markwolff May 28, 2020
b6ebf91
add react to rush
markwolff May 28, 2020
dce63b7
fix rollup
markwolff May 28, 2020
3729221
Revert "fix rollup"
markwolff May 28, 2020
7f3952d
Revert "add react to rush"
markwolff May 28, 2020
23cfaa2
Merge branch 'master' into aaronpowell/issue-991
May 29, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 22 additions & 6 deletions common/config/rush/npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

100 changes: 51 additions & 49 deletions extensions/applicationinsights-react-js/package.json
@@ -1,51 +1,53 @@
{
"name": "@microsoft/applicationinsights-react-js",
"version": "2.3.1",
"description": "Microsoft Application Insights React plugin",
"main": "dist/applicationinsights-react-js.js",
"module": "dist-esm/applicationinsights-react-js.js",
"types": "types/applicationinsights-react-js.d.ts",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/microsoft/ApplicationInsights-JS/tree/master/extensions/applicationinsights-react-js"
},
"scripts": {
"build": "npm run build:esm && npm run build:browser",
"build:esm": "grunt react",
"build:browser": "rollup -c",
"test": "jest --config test/jestconfig.json",
"test-watch": "jest --config test/jestconfig.json --watch",
"lint": "tslint -p tsconfig.json"
},
"devDependencies": {
"@types/node": "11.13.2",
"@types/enzyme": "3.1.8",
"@types/history": "4.7.2",
"@types/react": "16.0.36",
"@types/react-dom": "16.0.3",
"@types/jest": "^24.0.11",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.12.1",
"jest": "^24.7.1",
"grunt": "1.0.1",
"ts-jest": "^24.0.2",
"react-dom": "^16.8.6",
"rollup": "^0.66.0",
"rollup-plugin-node-resolve": "^3.4.0",
"rollup-plugin-replace": "^2.1.0",
"rollup-plugin-uglify": "^6.0.0",
"rollup-plugin-commonjs": "^9.3.4",
"typescript": "2.5.3",
"tslint": "^5.19.0",
"tslint-config-prettier": "^1.18.0"
},
"dependencies": {
"@microsoft/applicationinsights-core-js": "2.3.1",
"@microsoft/applicationinsights-common": "2.3.1",
"tslib": "^1.9.3",
"react": "^16.8.6",
"history": "^4.9.0"
},
"license": "MIT"
"name": "@microsoft/applicationinsights-react-js",
"version": "2.3.1",
"description": "Microsoft Application Insights React plugin",
"main": "dist/applicationinsights-react-js.js",
"module": "dist-esm/applicationinsights-react-js.js",
"types": "types/applicationinsights-react-js.d.ts",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/microsoft/ApplicationInsights-JS/tree/master/extensions/applicationinsights-react-js"
},
"scripts": {
"build": "npm run build:esm && npm run build:browser",
"build:esm": "grunt react",
"build:browser": "rollup -c",
"test": "jest --config test/jestconfig.json",
"test-watch": "jest --config test/jestconfig.json --watch",
"lint": "tslint -p tsconfig.json"
},
"license": "MIT",
"dependencies": {
"@microsoft/applicationinsights-common": "2.3.1",
"@microsoft/applicationinsights-core-js": "2.3.1",
"history": "^4.9.0",
"react": "^16.8.6",
"tslib": "^1.9.3"
},
"devDependencies": {
"@types/enzyme": "3.1.8",
"@types/history": "4.7.2",
"@types/jest": "^24.0.11",
"@types/node": "11.13.2",
"@types/prop-types": "~15.7.3",
"@types/react": "~16.9.11",
"@types/react-dom": "~16.9.4",
"csstype": "~2.6.7",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.12.1",
"grunt": "1.0.1",
"jest": "^24.7.1",
"react-dom": "^16.8.6",
"rollup": "^0.66.0",
"rollup-plugin-commonjs": "^9.3.4",
"rollup-plugin-node-resolve": "^3.4.0",
"rollup-plugin-replace": "^2.1.0",
"rollup-plugin-uglify": "^6.0.0",
"ts-jest": "^24.0.2",
"tslint": "^5.19.0",
"tslint-config-prettier": "^1.18.0",
"typescript": "2.5.3"
}
}
@@ -0,0 +1,8 @@
import { createContext, useContext } from "react";
import ReactPlugin from "./ReactPlugin";

const AppInsightsContext = createContext<ReactPlugin>(undefined);

const useAppInsightsContext = () => useContext(AppInsightsContext);

export { AppInsightsContext, useAppInsightsContext };
11 changes: 10 additions & 1 deletion extensions/applicationinsights-react-js/src/ReactPlugin.ts
Expand Up @@ -4,7 +4,7 @@
*/

import {
IConfig, IPageViewTelemetry, IMetricTelemetry, IAppInsights
IConfig, IPageViewTelemetry, IMetricTelemetry, IAppInsights, IEventTelemetry
} from "@microsoft/applicationinsights-common";
import {
IPlugin, IConfiguration, IAppInsightsCore,
Expand Down Expand Up @@ -81,6 +81,15 @@ export default class ReactPlugin implements ITelemetryPlugin {
}
}

trackEvent(event: IEventTelemetry, customProperties?: ICustomProperties) {
if (this._analyticsPlugin) {
this._analyticsPlugin.trackEvent(event, customProperties);
} else {
this._logger.throwInternal(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this then becomes

this.diagLog().throwInternal(...)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not seeing any other blocking issues apart from the intro of the _logger.
For references the diaLog() takes care of the this getting called before initialization() as well.

LoggingSeverity.CRITICAL, _InternalMessageId.TelemetryInitializerFailed, "Analytics plugin is not available, React plugin telemetry will not be sent: ");
}
}

private addHistoryListener(history: History): void {
const locationListener: LocationListener = (location: Location, action: Action): void => {
// Timeout to ensure any changes to the DOM made by route changes get included in pageView telemetry
Expand Down
Expand Up @@ -4,5 +4,19 @@
import { IReactExtensionConfig } from "./Interfaces/IReactExtensionConfig";
import ReactPlugin from "./ReactPlugin";
import withAITracking from "./withAITracking";
import {
AppInsightsContext,
useAppInsightsContext
} from "./AppInsightsContext";
import useTrackEvent from "./useTrackEvent";
import useTrackMetric from "./useTrackMetric";

export { ReactPlugin, IReactExtensionConfig, withAITracking };
export {
ReactPlugin,
IReactExtensionConfig,
withAITracking,
AppInsightsContext,
useAppInsightsContext,
useTrackEvent,
useTrackMetric
};
26 changes: 26 additions & 0 deletions extensions/applicationinsights-react-js/src/useTrackEvent.ts
@@ -0,0 +1,26 @@
/**
* ReactPlugin.ts
* @copyright Microsoft 2019
*/
import { useState, useEffect, useRef } from "react";
import ReactPlugin from "./ReactPlugin";

export default function useCustomEvent<T>(
reactPlugin: ReactPlugin,
eventName: string,
eventData: T,
skipFirstRun = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aaronpowell do you mind explaining why this skipFirstRun flag is needed here? thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xiao-lix The reason is to mimic more closely the way the non-hook version works. With useEffect hooks the effect is triggered on each value update including the initial setting of the value, meaning we would fire off too early and potentially have unwanted events tracked. I explain it more in the post I wrote: https://www.aaron-powell.com/posts/2019-11-19-combining-react-hooks-with-appinsights/#ignoring-unwanted-effect-runs

) {
const [data, setData] = useState(eventData);
const firstRun = useRef(skipFirstRun);

useEffect(() => {
if (firstRun.current) {
firstRun.current = false;
return;
}
reactPlugin.trackEvent({ name: eventName }, data);
}, [reactPlugin, data, eventName]);

return setData;
}
103 changes: 103 additions & 0 deletions extensions/applicationinsights-react-js/src/useTrackMetric.ts
@@ -0,0 +1,103 @@
import { useEffect, useRef } from "react";
import ReactPlugin from "./ReactPlugin";

interface ITrackedData {
hookTimestamp: number;
firstActiveTimestamp: number;
totalIdleTime: number;
lastActiveTimestamp: number;
idleStartTimestamp: number;
idleCount: number;
idleTimeout: number;
}

function getEngagementTimeSeconds(trackedData: ITrackedData) {
return (
(Date.now() -
trackedData.firstActiveTimestamp -
trackedData.totalIdleTime -
trackedData.idleCount * trackedData.idleTimeout) /
1000
);
}

const useComponentTracking = (
reactPlugin: ReactPlugin,
componentName: string
) => {
const tracking = useRef<ITrackedData>({
hookTimestamp: Date.now(),
firstActiveTimestamp: 0,
totalIdleTime: 0,
lastActiveTimestamp: 0,
idleStartTimestamp: 0,
idleCount: 0,
idleTimeout: 5000
});
const savedCallback = useRef<() => void>();

const callback = () => {
let trackedData = tracking.current;
if (
trackedData.lastActiveTimestamp > 0 &&
trackedData.idleStartTimestamp === 0 &&
Date.now() - trackedData.lastActiveTimestamp >= trackedData.idleTimeout
) {
trackedData.idleStartTimestamp = Date.now();
trackedData.idleCount++;
}
};
const delay = 100;

savedCallback.current = callback;

// Set up the interval.
useEffect(() => {
let id = setInterval(savedCallback.current, delay);
return () => {
clearInterval(id);

let trackedData = tracking.current;
if (trackedData.hookTimestamp === 0) {
throw new Error(
"useAppInsights:unload hook: hookTimestamp is not initialized."
);
}

if (trackedData.firstActiveTimestamp === 0) {
return;
}

const engagementTime = getEngagementTimeSeconds(trackedData);
const metricData = {
average: engagementTime,
name: "React Component Engaged Time (seconds)",
sampleCount: 1
};

const additionalProperties = { "Component Name": componentName };
reactPlugin.trackMetric(metricData, additionalProperties);
};
}, []);

const trackActivity = () => {
let trackedData = tracking.current;
if (trackedData.firstActiveTimestamp === 0) {
trackedData.firstActiveTimestamp = Date.now();
trackedData.lastActiveTimestamp = trackedData.firstActiveTimestamp;
} else {
trackedData.lastActiveTimestamp = Date.now();
}

if (trackedData.idleStartTimestamp > 0) {
const lastIdleTime =
trackedData.lastActiveTimestamp - trackedData.idleStartTimestamp;
trackedData.totalIdleTime += lastIdleTime;
trackedData.idleStartTimestamp = 0;
}
};

return trackActivity;
};

export default useComponentTracking;
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.