Skip to content
This repository has been archived by the owner on May 22, 2019. It is now read-only.

Unable to track changes in page name #83

Open
mikegoatly opened this issue Apr 25, 2019 · 4 comments
Open

Unable to track changes in page name #83

mikegoatly opened this issue Apr 25, 2019 · 4 comments
Labels

Comments

@mikegoatly
Copy link

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 <title> of the page, and it looks like AI is using the current page title as the name.

I'm using react-router and that doesn't seem to have a mechanism to set the page title.

I've tried to use react-helmet 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.

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?

@hiraldesai
Copy link
Collaborator

I have used react-document-title 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.

The way I'm using it is like this inside my main Routes.tsx page that defines all application routes.

image

And the generateTitle function looks like this.

  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}`;
  }

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).

When I was working on this feature, I noticed that pageView registration in AI was recording the new URL but previous page title for me. So I explicitly added 500ms delay to ensure that the AI tracking call received the latest DOM updates.

@yasso1am
Copy link

yasso1am commented Apr 26, 2019

@mikegoatly I was able to use react-helmet to accomplish exactly what you're trying to do.

Here is my appinsights.ts

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 };

the history object I'm importing from my history.ts file looks like this:

import { createBrowserHistory } from 'history';

export const history = createBrowserHistory();

It's the same history object I'm importing in index.tsx and passing to my <Router history={history}

and finally here is the return statement from a simple component where I am using React Helmet

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>
      </>
    );

Here is a screenshot of what I get back from AppInsights Analytics

image

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.

@mikegoatly
Copy link
Author

Thanks both for the responses - it's good to know I was along the right sort of lines.

@hiraldesai I think the problem that I'm running into is that 500ms might not always 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.

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.

I'll do a bit more experimentation.

@mikegoatly
Copy link
Author

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 title DOM element.

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.

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 doesn't 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...

In case it's helpful, my wrapper class for AI looks something like this:

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 });
        }
    }

...
}

Where the debounce helper function is used to prevent rapid successive changes to title causing suprious page change notifications:

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);
    };
};

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants