diff --git a/packages/opencensus-web-instrumentation-zone/src/history-api-patch.ts b/packages/opencensus-web-instrumentation-zone/src/history-api-patch.ts new file mode 100644 index 00000000..638134bb --- /dev/null +++ b/packages/opencensus-web-instrumentation-zone/src/history-api-patch.ts @@ -0,0 +1,85 @@ +/** + * Copyright 2019, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { tracing } from '@opencensus/web-core'; +import { isRootSpanNameReplaceable } from './util'; + +/** + * Monkey-patch `History API` to detect route transitions. This is necessary + * because there might be some cases when there are several interactions being + * tracked at the same time but if there is an user interaction that triggers a + * route transition while those interactions are still in tracking, only that + * interaction will have a `Navigation` name. Otherwise, if this is not patched + * the other interactions will change the name to `Navigation` even if they did + * not cause the route transition. + */ +export function patchHistoryApi() { + const pushState = history.pushState; + history.pushState = ( + data: unknown, + title: string, + url?: string | null | undefined + ) => { + patchHistoryApiMethod(pushState, data, title, url); + }; + + const replaceState = history.replaceState; + history.replaceState = ( + data: unknown, + title: string, + url?: string | null | undefined + ) => { + patchHistoryApiMethod(replaceState, data, title, url); + }; + + const back = history.back; + history.back = () => { + patchHistoryApiMethod(back); + }; + + const forward = history.forward; + history.forward = () => { + patchHistoryApiMethod(forward); + }; + + const go = history.go; + history.go = (delta?: number) => { + patchHistoryApiMethod(go, delta); + }; + + const patchHistoryApiMethod = (func: Function, ...args: unknown[]) => { + // Store the location.pathname before it changes calling `func`. + const currentPathname = location.pathname; + func.call(history, ...args); + maybeUpdateInteractionName(currentPathname); + }; +} + +function maybeUpdateInteractionName(previousLocationPathname: string) { + const rootSpan = tracing.tracer.currentRootSpan; + // If for this interaction, the developer did not give any explicit + // attibute (`data-ocweb-id`) and the generated name can be replaced, + // that means the name might change to `Navigation ` as this is a + // more understadable name for the interaction in case the location + // pathname actually changed. + if ( + rootSpan && + isRootSpanNameReplaceable(Zone.current) && + previousLocationPathname !== location.pathname + ) { + rootSpan.name = 'Navigation ' + location.pathname; + } +} diff --git a/packages/opencensus-web-instrumentation-zone/src/interaction-tracker.ts b/packages/opencensus-web-instrumentation-zone/src/interaction-tracker.ts index 200abb38..c614fac7 100644 --- a/packages/opencensus-web-instrumentation-zone/src/interaction-tracker.ts +++ b/packages/opencensus-web-instrumentation-zone/src/interaction-tracker.ts @@ -20,13 +20,13 @@ import { SpanKind, RootSpan, } from '@opencensus/web-core'; -import { AsyncTask } from './zone-types'; +import { AsyncTask, InteractionName } from './zone-types'; import { OnPageInteractionStopwatch, startOnPageInteraction, } from './on-page-interaction-stop-watch'; -import { isTrackedTask } from './util'; +import { isTrackedTask, getTraceId, resolveInteractionName } from './util'; import { interceptXhrTask } from './xhr-interceptor'; // Allows us to monkey patch Zone prototype without TS compiler errors. @@ -59,7 +59,6 @@ export class InteractionTracker { this.patchZoneRunTask(); this.patchZoneScheduleTask(); this.patchZoneCancelTask(); - this.patchHistoryApi(); } static startTracking(): void { @@ -147,11 +146,11 @@ export class InteractionTracker { interceptingElement: HTMLElement, eventName: string, taskZone: Zone, - interactionName: string + interactionName: InteractionName ) { const traceId = randomTraceId(); const spanOptions = { - name: interactionName, + name: interactionName.name, spanContext: { traceId, // This becomes the parentSpanId field of the root span, and the actual @@ -168,6 +167,8 @@ export class InteractionTracker { // to capture the new zone, also, start the `OnPageInteraction` to capture the // new root span. this.currentEventTracingZone = Zone.current; + this.currentEventTracingZone.get('data')['isRootSpanNameReplaceable'] = + interactionName.isReplaceable; this.interactions[traceId] = startOnPageInteraction({ startLocationHref: location.href, startLocationPath: location.pathname, @@ -266,81 +267,6 @@ export class InteractionTracker { stopWatch.stopAndRecord(); delete this.interactions[interactionId]; } - - // Monkey-patch `History API` to detect route transitions. - // This is necessary because there might be some cases when - // there are several interactions being tracked at the same time - // but if there is an user interaction that triggers a route transition - // while those interactions are still in tracking, only that interaction - // will have a `Navigation` name. Otherwise, if this is not patched, the - // other interactions will change the name to `Navigation` even if they - // did not cause the route transition. - private patchHistoryApi() { - const pushState = history.pushState; - history.pushState = ( - data: unknown, - title: string, - url?: string | null | undefined - ) => { - patchHistoryApiMethod(pushState, data, title, url); - }; - - const replaceState = history.replaceState; - history.replaceState = ( - data: unknown, - title: string, - url?: string | null | undefined - ) => { - patchHistoryApiMethod(replaceState, data, title, url); - }; - - const back = history.back; - history.back = () => { - patchHistoryApiMethod(back); - }; - - const forward = history.forward; - history.forward = () => { - patchHistoryApiMethod(forward); - }; - - const go = history.go; - history.go = (delta?: number) => { - patchHistoryApiMethod(go, delta); - }; - - const patchHistoryApiMethod = (func: Function, ...args: unknown[]) => { - // Store the location.pathname before it changes calling `func`. - const currentPathname = location.pathname; - func.call(history, ...args); - this.maybeUpdateInteractionName(currentPathname); - }; - } - - private maybeUpdateInteractionName(previousLocationPathname: string) { - const rootSpan = tracing.tracer.currentRootSpan; - // If for this interaction, the developer did not give any - // explicit attibute (`data-ocweb-id`) the current interaction - // name will start with a '<' that stands to the tag name. If that is - // the case, change the name to `Navigation ` as this is a more - // understadable name for the interaction. - // Also, we check if the location pathname did change. - if ( - rootSpan && - rootSpan.name.startsWith('<') && - previousLocationPathname !== location.pathname - ) { - rootSpan.name = 'Navigation ' + location.pathname; - } - } -} - -/** - * Get the trace ID from the zone properties. - * @param zone - */ -function getTraceId(zone: Zone): string { - return zone && zone.get('data') ? zone.get('data').traceId : ''; } function getTrackedElement(task: AsyncTask): HTMLElement | null { @@ -349,39 +275,6 @@ function getTrackedElement(task: AsyncTask): HTMLElement | null { return task.target as HTMLElement; } -/** - * Look for 'data-ocweb-id' attibute in the HTMLElement in order to - * give a name to the user interaction and Root span. If this attibute is - * not present, use the element ID, tag name, event that triggered the interaction. - * Thus, the resulting interaction name will be: "tag_name> id:'ID' event" - * (e.g. "