Azure / react-appinsights

Typescript library to use Application Insights in React applications [deprecated, see microsoft/ApplicationInsights-JS]
MIT License
78 stars 32 forks source link

Unable to track changes in page name #83

Open mikegoatly opened 5 years ago

mikegoatly commented 5 years ago

Version: 3.0.0-rc.6

I noticed that every page view was being tracked with the same page name - it didn't take long to realise it was because I wasn't changing the of the page, and it looks like AI is using the current page title as the name.</p> <p>I'm using react-router and that doesn't <a rel="noreferrer nofollow" target="_blank" href="https://github.com/ReactTraining/react-router/issues/2844">seem to have a mechanism to set the page title</a>.</p> <p>I've tried to use <a rel="noreferrer nofollow" target="_blank" href="https://github.com/nfl/react-helmet">react-helmet</a> to set the title when a component is loaded, but it seems this is too late and the page title has already been captured to send with the telemetry.</p> <p>Has anyone else come up with an approach that will allow for the page title to be updated when a route changes and be captured correctly in page view analytics?</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/hiraldesai"><img src="https://avatars.githubusercontent.com/u/1005174?v=4" />hiraldesai</a> commented <strong> 5 years ago</strong> </div> <div class="markdown-body"> <p>I have used <a href="https://www.npmjs.com/package/react-document-title">react-document-title</a> in one of my apps with success. I don't see any reason why it shouldn't work with react-helmet either. As long as your page's title in DOM changes within 500ms of the route change, you should be good.</p> <p>The way I'm using it is like this inside my main <code>Routes.tsx</code> page that defines all application routes.</p> <p><img src="https://user-images.githubusercontent.com/1005174/56775663-e493ae00-677c-11e9-9532-a7e8f01b82a3.png" alt="image" /></p> <p>And the <code>generateTitle</code> function looks like this. </p> <pre><code class="language-typescript"> private generateTitle(): string { const { pathname } = this.props; const baseTitle = localeService.getText("DocumentTitles", "/"); let pathTitle = pathname !== "/" ? localeService.getText("DocumentTitles", pathname) : null; pathTitle = pathTitle ? ` - ${pathTitle}` : ""; return `${baseTitle}${pathTitle}`; }</code></pre> <p>We use a locale service to load a JSON keyvalue pairs of url -> title for the current locale (as my app needs to support multiple country/languages).</p> <p>When I was working on this feature, I noticed that <code>pageView</code> registration in AI was recording the new URL but previous page title for me. So I explicitly added <a href="https://github.com/Azure/react-appinsights/blob/a8adfc8b807ab3b97b71b139ea10327a32588c0d/src/ReactAI.ts#L143">500ms delay</a> to ensure that the AI tracking call received the latest DOM updates.</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/yasso1am"><img src="https://avatars.githubusercontent.com/u/39067243?v=4" />yasso1am</a> commented <strong> 5 years ago</strong> </div> <div class="markdown-body"> <p>@mikegoatly I was able to use <code>react-helmet</code> to accomplish exactly what you're trying to do.</p> <p>Here is my <code>appinsights.ts</code></p> <pre><code>import env from '../environment'; import { reactAI } from 'react-appinsights'; import { ApplicationInsights } from '@microsoft/applicationinsights-web'; import { history } from './history'; class Insights { private ai: ApplicationInsights; constructor() { this.ai = new ApplicationInsights({ config: { instrumentationKey: `${env.appInsights.instrumentationKey}`, extensions: [reactAI], extensionConfig: { [reactAI.extensionId]: { debug: true, history: history, } } } }); this.ai.loadAppInsights(); } public setAuthenticatedUserContext = (patientId: string) => { this.ai.setAuthenticatedUserContext(patientId, undefined, true); }; public clearAuthenticatedUserContext = () => { this.ai.clearAuthenticatedUserContext(); }; } const AppInsightsService = new Insights(); export { AppInsightsService };</code></pre> <p>the <code>history</code> object I'm importing from my <code>history.ts</code> file looks like this:</p> <pre><code>import { createBrowserHistory } from 'history'; export const history = createBrowserHistory();</code></pre> <p>It's the same history object I'm importing in <code>index.tsx</code> and passing to my <code><Router history={history}</code></p> <p>and finally here is the return statement from a simple component where I am using React Helmet </p> <pre><code>return ( <> <Helmet> <title>Forgot Password</title> </Helmet> <div> <span>{this.state.response}</span> <form ref={this.forgotPasswordForm} onSubmit={this.changePassword}> <label> email: <input type="email" required name="email" value={email} onChange={this.handleChange}/> </label> <input type="submit" value="Submit"/> </form> <Link to="/login">Login</Link> </div> </> );</code></pre> <p>Here is a screenshot of what I get back from AppInsights Analytics</p> <p><img src="https://user-images.githubusercontent.com/39067243/56776509-a64cbd80-6781-11e9-9bf7-0c4f627d4137.png" alt="image" /></p> <p>The first name is Byte, and is unavoidable I guess because that's what <title> is set to in my index.html, but if I navigate to any page after that (like forgot password) it will pick up the appropriate name.</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/mikegoatly"><img src="https://avatars.githubusercontent.com/u/4577868?v=4" />mikegoatly</a> commented <strong> 5 years ago</strong> </div> <div class="markdown-body"> <p>Thanks both for the responses - it's good to know I was along the right sort of lines.</p> <p>@hiraldesai I think the problem that I'm running into is that 500ms might not <em>always</em> be enough - I'm using react lazy loading for some routes. On first load the title won't update until the resources are loaded and this would be dependent on network speed.</p> <p>In a dev environment the chunks take ~1s for me, so it's always a problem on first load. I'm not so worried about this case, but it does highlight the problem.</p> <p>I'll do a bit more experimentation.</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/mikegoatly"><img src="https://avatars.githubusercontent.com/u/4577868?v=4" />mikegoatly</a> commented <strong> 5 years ago</strong> </div> <div class="markdown-body"> <p>Ok, in order to get this working in a deterministic fashion I've had to remove history tracking and rely on observing changes to the <code>title</code> DOM element.</p> <p>This isn't a panacea as it won't handle the case when a history navigation occurs and the resulting page doesn't affect the page title. I can live with that in my application.</p> <p>I'd love to be able combine the two approaches, i.e. a history navigation occurs, and we then wait for the title to change, and if the title <em>doesn't</em> change after a while then report a navigation anyway, but the problem still stands - I've no idea how long to wait for the lazily loaded components to finish loading. My react-fu isn't strong enough to know if there's a better way involving React Suspense...</p> <p>In case it's helpful, my wrapper class for AI looks something like this:</p> <pre><code class="language-typescript">class PortalInsights { private ai: IApplicationInsights | null = null private titleChangeObserver: MutationObserver; constructor() { this.initialize = this.initialize.bind(this); this.trackPageView = debounce(this.trackPageView, 100).bind(this); this.titleChangeObserver = new MutationObserver(this.trackPageView); } public initialize(user: User) { const { profile: { email, sub: id } } = user; var appInsights = new ApplicationInsights({ config: { instrumentationKey: appInsightsId, extensions: [reactAI], extensionConfig: { [reactAI.extensionId]: { debug: devenv } } } }); this.ai = appInsights.loadAppInsights(); appInsights.setAuthenticatedUserContext(id.toString()); const titleNode = document.querySelector('title'); if (titleNode != null) { this.titleChangeObserver.observe( titleNode, { childList: true, characterData: true, subtree: true } ); } } public trackPageView() { const pathname = window.location.pathname; const name = document.title; if (name != null && name.length > 0 && this.ai == null) { this.ai.trackPageView({ uri: pathname, name }); } } ... }</code></pre> <p>Where the <code>debounce</code> helper function is used to prevent rapid successive changes to title causing suprious page change notifications:</p> <pre><code>function debounce(func: () => void, wait: number) { let timeout: NodeJS.Timeout | null; return function (this: any) { const context: any = this; const args: any = arguments; if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { timeout = null; func.apply(context, args); }, wait); }; };</code></pre> </div> </div> <div class="page-bar-simple"> </div> <div class="footer"> <ul class="body"> <li>© <script> document.write(new Date().getFullYear()) </script> Githubissues.</li> <li>Githubissues is a development platform for aggregating issues.</li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script> <script src="/githubissues/assets/js.js"></script> <script src="/githubissues/assets/markdown.js"></script> <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.4.0/build/highlight.min.js"></script> <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.4.0/build/languages/go.min.js"></script> <script> hljs.highlightAll(); </script> </body> </html>