Skip to content

Commit

Permalink
Extracting HOC tracked component class base for re-use (#1829)
Browse files Browse the repository at this point in the history
* Extracting HOC tracked component class  for re-use

* addressing review comments

Co-authored-by: Ladislav Bitto <labitto@microsoft.com>
Co-authored-by: Nev <54870357+MSNev@users.noreply.github.com>
  • Loading branch information
3 people authored May 5, 2022
1 parent 9d4e266 commit 4391b21
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { IReactExtensionConfig } from "./Interfaces/IReactExtensionConfig";
import ReactPlugin from "./ReactPlugin";
import withAITracking from "./withAITracking";
import withAITracking, { AITrackedComponentBase } from "./withAITracking";
import AppInsightsErrorBoundary from "./AppInsightsErrorBoundary"
import {
AppInsightsContext,
Expand All @@ -20,5 +20,6 @@ export {
AppInsightsContext,
useAppInsightsContext,
useTrackEvent,
useTrackMetric
useTrackMetric,
AITrackedComponentBase
};
160 changes: 90 additions & 70 deletions extensions/applicationinsights-react-js/src/withAITracking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,92 @@
// Licensed under the MIT License.

import { IMetricTelemetry } from '@microsoft/applicationinsights-common';
import { CoreUtils } from '@microsoft/applicationinsights-core-js';
import { dateNow } from '@microsoft/applicationinsights-core-js';
import * as React from 'react';
import ReactPlugin from './ReactPlugin';

/**
* Higher-order component base class to hook Application Insights tracking
* in a React component's lifecycle.
*/
export abstract class AITrackedComponentBase<P> extends React.Component<P> {
protected _mountTimestamp: number = 0;
protected _firstActiveTimestamp: number = 0;
protected _idleStartTimestamp: number = 0;
protected _lastActiveTimestamp: number = 0;
protected _totalIdleTime: number = 0;
protected _idleCount: number = 0;
protected _idleTimeout: number = 5000;
protected _intervalId?: any;
protected _componentName: string;
protected _reactPlugin: ReactPlugin;

public constructor(props: P, reactPlugin: ReactPlugin, componentName: string) {
super(props);

this._reactPlugin = reactPlugin;
this._componentName = componentName;
}

public componentDidMount() {
this._mountTimestamp = dateNow();
this._firstActiveTimestamp = 0;
this._totalIdleTime = 0;
this._lastActiveTimestamp = 0;
this._idleStartTimestamp = 0;
this._idleCount = 0;

this._intervalId = setInterval(() => {
if (this._lastActiveTimestamp > 0 && this._idleStartTimestamp === 0 && dateNow() - this._lastActiveTimestamp >= this._idleTimeout) {
this._idleStartTimestamp = dateNow();
this._idleCount++;
}
}, 100);
}

public componentWillUnmount() {
if (this._mountTimestamp === 0) {
throw new Error('withAITracking:componentWillUnmount: mountTimestamp is not initialized.');
}
if (this._intervalId) {
clearInterval(this._intervalId);
}

if (this._firstActiveTimestamp === 0) {
return;
}

const engagementTime = this.getEngagementTimeSeconds();
const metricData: IMetricTelemetry = {
average: engagementTime,
name: 'React Component Engaged Time (seconds)',
sampleCount: 1
};

const additionalProperties: { [key: string]: any } = { 'Component Name': this._componentName };
this._reactPlugin.trackMetric(metricData, additionalProperties);
}

protected trackActivity = (e: React.SyntheticEvent<any>): void => {
if (this._firstActiveTimestamp === 0) {
this._firstActiveTimestamp = dateNow();
this._lastActiveTimestamp = this._firstActiveTimestamp;
} else {
this._lastActiveTimestamp = dateNow();
}

if (this._idleStartTimestamp > 0) {
const lastIdleTime = this._lastActiveTimestamp - this._idleStartTimestamp;
this._totalIdleTime += lastIdleTime;
this._idleStartTimestamp = 0;
}
}

private getEngagementTimeSeconds(): number {
return (dateNow() - this._firstActiveTimestamp - this._totalIdleTime - this._idleCount * this._idleTimeout) / 1000;
}
}

/**
* Higher-order component function to hook Application Insights tracking
* in a React component's lifecycle.
Expand All @@ -18,63 +100,20 @@ import ReactPlugin from './ReactPlugin';
export default function withAITracking<P>(reactPlugin: ReactPlugin, Component: React.ComponentType<P>, componentName?: string, className?: string): React.ComponentClass<P> {

if (componentName === undefined || componentName === null || typeof componentName !== 'string') {
componentName = Component.prototype &&
Component.prototype.constructor &&
Component.prototype.constructor.name ||
'Unknown';
componentName = Component.prototype &&
Component.prototype.constructor &&
Component.prototype.constructor.name ||
'Unknown';
}

if (className === undefined || className === null || typeof className !== 'string') {
className = '';
}

return class extends React.Component<P> {
private _mountTimestamp: number = 0;
private _firstActiveTimestamp: number = 0;
private _idleStartTimestamp: number = 0;
private _lastActiveTimestamp: number = 0;
private _totalIdleTime: number = 0;
private _idleCount: number = 0;
private _idleTimeout: number = 5000;
private _intervalId?: any;

public componentDidMount() {
this._mountTimestamp = CoreUtils.dateNow();
this._firstActiveTimestamp = 0;
this._totalIdleTime = 0;
this._lastActiveTimestamp = 0;
this._idleStartTimestamp = 0;
this._idleCount = 0;

this._intervalId = setInterval(() => {
if (this._lastActiveTimestamp > 0 && this._idleStartTimestamp === 0 && CoreUtils.dateNow() - this._lastActiveTimestamp >= this._idleTimeout) {
this._idleStartTimestamp = CoreUtils.dateNow();
this._idleCount++;
}
}, 100);
}

public componentWillUnmount() {
if (this._mountTimestamp === 0) {
throw new Error('withAITracking:componentWillUnmount: mountTimestamp is not initialized.');
}
if (this._intervalId) {
clearInterval(this._intervalId);
}
return class extends AITrackedComponentBase<P> {

if (this._firstActiveTimestamp === 0) {
return;
}

const engagementTime = this.getEngagementTimeSeconds();
const metricData: IMetricTelemetry = {
average: engagementTime,
name: 'React Component Engaged Time (seconds)',
sampleCount: 1
};

const additionalProperties: { [key: string]: any } = { 'Component Name': componentName };
reactPlugin.trackMetric(metricData, additionalProperties);
public constructor(props: P) {
super(props, reactPlugin, componentName);
}

public render() {
Expand All @@ -92,24 +131,5 @@ export default function withAITracking<P>(reactPlugin: ReactPlugin, Component: R
</div>
);
}

private trackActivity = (e: React.SyntheticEvent<any>): void => {
if (this._firstActiveTimestamp === 0) {
this._firstActiveTimestamp = CoreUtils.dateNow();
this._lastActiveTimestamp = this._firstActiveTimestamp;
} else {
this._lastActiveTimestamp = CoreUtils.dateNow();
}

if (this._idleStartTimestamp > 0) {
const lastIdleTime = this._lastActiveTimestamp - this._idleStartTimestamp;
this._totalIdleTime += lastIdleTime;
this._idleStartTimestamp = 0;
}
}

private getEngagementTimeSeconds(): number {
return (CoreUtils.dateNow() - this._firstActiveTimestamp - this._totalIdleTime - this._idleCount * this._idleTimeout) / 1000;
}
}
}

0 comments on commit 4391b21

Please sign in to comment.