EmmanuelRoux / ngx-matomo-client

Matomo analytics client for Angular applications
https://matomo.org/
MIT License
74 stars 14 forks source link

Issue using tracker.setUserId() #46

Closed awdorrin closed 2 years ago

awdorrin commented 2 years ago

I am trying to use the tracker setUserId method to provide usernames to Matomo and have created a MatomoRouterInterceptor that looks like this:

@Injectable()
export class MatomoInterceptor implements MatomoRouterInterceptor {
  constructor(
  private readonly tracker: MatomoTracker,
    private userService: UserService
  ) { }

  beforePageTrack(event: NavigationEnd): void {
    this.tracker.setUserId(this.userService.getCurrentUser().ntid);
  }
}

Unfortunately, it does not seem to work until the second navigation within the application. The initial entry into the app, does not include the 'uid' field in the request payload.

Also, I am getting an error in the dev console that says, which has be curious.

matomo.js:22 The method enableLinkTracking is registered more than once in "_paq" variable. Only the last call has an effect. Please have a look at the multiple Piwik trackers documentation:
 https://developer.piwik.org/guides/tracking-javascript-guide#multiple-piwik-trackers

Thanks - Al

EmmanuelRoux commented 2 years ago

Hi @awdorrin, have you enabled trackAppInitialLoad? (not recommended)

awdorrin commented 2 years ago

I have not intentionally. (I searched my code and found no references to 'trackAppInitialLoad')

EmmanuelRoux commented 2 years ago

Ok, can you please provide a repro (on Stackblitz for ex.) so we can reproduce and debug?

awdorrin commented 2 years ago

I don't have a public Matomo server to point to, but I think this represents what I'm trying to do: Editor URL: https://stackblitz.com/edit/angular-ivy-n2kyst?file=src/index.html App URL: https://angular-ivy-n2kyst.stackblitz.io

EmmanuelRoux commented 2 years ago

There's an error in routing configuration: you're declaring your root component (AppComponent) as a routed component. Find angular docs here: https://angular.io/guide/router

BTW, I've added some logs into your example, and I can see the correct tracking functions calls.

On initial load image

Then I navigate to "Hello": image

awdorrin commented 2 years ago

Having AppComponent in the routing table isn't how our real application is configured, I should re-work the sample.

awdorrin commented 2 years ago

I revised my example to have a HomeComponent as the default route, instead of AppComponent. I then tried pointing to the 'https://ngx.matomo.cloud/matomo.js site - but I'm not sure what version of Matomo that site is providing. (Our Matomo server is Version 3.)

On Stackblitz, I do see the 'uid' value in the initial route, so not sure why it isn't working in our app. Unless it is because in this sample, I am just returning a string for the userId, when in our real app, we are hitting a server endpoint to query for the authentication information, and the response is delayed.

I didn't see new code for logging in my example - not sure if you worked in a branch or not?

awdorrin commented 2 years ago

After setting 'enableTracing' in the router, I see that our User Service hasn't received the response from the server, before the routing to initial page has completed. So the 'tracker.setUserId' call must be setting it to an empty value on the first call, which I assume is the same as clearing it. Without the router tracking messages, I was mislead by what was in our log, and thought that the user query was complete already.

There must be a way to delay the navigation to the first page, until we have the user's identity... Joys of sync vs async programming.

I'll close this out, since this isn't an issue with the ngx-matomo api.

Thanks!

EmmanuelRoux commented 2 years ago

@awdorrin Solution is very simple: you can return an Observalbe or Promise from interceptor ;-) If you do so, then ngx-matomo Router will wait your asynchronous operation to finish, before tracking page view.

Example:

@Injectable()
export class MatomoInterceptor implements MatomoRouterInterceptor {
  constructor(
  private readonly tracker: MatomoTracker,
    private userService: UserService
  ) { }

  beforePageTrack(event: NavigationEnd): Observable<void> {
    return this.userService.getCurrentUserAsynchronously().pipe(map(user => {
      this.tracker.setUserId(user.ntid);
      return undefined; // Interceptor just expect a value to be emitted, but it does not care about the value
    }));
  }
}

Or even simpler:

@Injectable()
export class MatomoInterceptor implements MatomoRouterInterceptor {
  constructor(
  private readonly tracker: MatomoTracker,
    private userService: UserService
  ) { }

  async beforePageTrack(event: NavigationEnd): Promise<void> {
    const user = await this.userService.getCurrentUserAsynchronously().pipe(first()).toPromise(); 

    this.tracker.setUserId(user.ntid);
  }
}
awdorrin commented 2 years ago

It seems when we first wrote the user service in this project, we didn't truly understand how HTTP requests were done in Angular. On our team, I bounce between several different projects, and sometimes it gets difficult to keep everything straight between them. I was started revising our user service to use a promise, instead of subscribe, and have been dealing with the refactoring involved across a dozen different components. What you suggest would have made things much easier!