Skip to content

ref: Change hooks to use simple verbs instead of before/-ed pairs #2675

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 16, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 41 additions & 34 deletions packages/integrations/src/vue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ interface IntegrationOptions {
logErrors: boolean;

/**
* When set to `false`, disables tracking of components lifecycle performance.
* By default, it tracks only when `Tracing` integration is also enabled.
* When set to `true`, enables tracking of components lifecycle performance.
* It requires `Tracing` integration to be also enabled.
*/
tracing: boolean;

Expand All @@ -75,9 +75,10 @@ interface TracingOptions {
timeout: number;
/**
* List of hooks to keep track of during component lifecycle.
* Available hooks: https://vuejs.org/v2/api/#Options-Lifecycle-Hooks
* Available hooks: 'activate' | 'create' | 'destroy' | 'mount' | 'update'
* Based on https://vuejs.org/v2/api/#Options-Lifecycle-Hooks
*/
hooks: Hook[];
hooks: Operation[];
}

/** Optional metadata attached to Sentry Event */
Expand All @@ -103,19 +104,13 @@ type Hook =

type Operation = 'activate' | 'create' | 'destroy' | 'mount' | 'update';

// Mappings from lifecycle hook to corresponding operation,
// used to track already started measurements.
const OPERATIONS: { [key in Hook]: Operation } = {
activated: 'activate',
beforeCreate: 'create',
beforeDestroy: 'destroy',
beforeMount: 'mount',
beforeUpdate: 'update',
created: 'create',
deactivated: 'activate',
destroyed: 'destroy',
mounted: 'mount',
updated: 'update',
// Mappings from operation to corresponding lifecycle hook.
const HOOKS: { [key in Operation]: Hook[] } = {
activate: ['activated', 'deactivated'],
create: ['beforeCreate', 'created'],
destroy: ['beforeDestroy', 'destroyed'],
mount: ['beforeMount', 'mounted'],
update: ['beforeUpdate', 'updated'],
};

const COMPONENT_NAME_REGEXP = /(?:^|[-_/])(\w)/g;
Expand Down Expand Up @@ -152,10 +147,10 @@ export class Vue implements Integration {
Vue: getGlobalObject<any>().Vue, // tslint:disable-line:no-unsafe-any
attachProps: true,
logErrors: false,
tracing: true,
tracing: false,
...options,
tracingOptions: {
hooks: ['beforeMount', 'mounted', 'beforeUpdate', 'updated'],
hooks: ['mount', 'update'],
timeout: 2000,
trackComponents: false,
...options.tracingOptions,
Expand Down Expand Up @@ -252,7 +247,7 @@ export class Vue implements Integration {
}
};

const childHandler = (hook: Hook) => {
const childHandler = (hook: Hook, operation: Operation) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an option we expose to make this per component? Like if a user wanted to track multiple hooks for a single component, but use the standard hooks: ['mount', 'update'] for everything else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's all configured for the whole app right now. We might do this in the future though.

// Skip components that we don't want to track to minimize the noise and give a more granular control to the user
const shouldTrack = Array.isArray(this._options.tracingOptions.trackComponents)
? this._options.tracingOptions.trackComponents.includes(name)
Expand All @@ -263,8 +258,7 @@ export class Vue implements Integration {
}

const now = timestampWithMs();
const op = OPERATIONS[hook];
const span = spans[op];
const span = spans[operation];

// On the first handler call (before), it'll be undefined, as `$once` will add it in the future.
// However, on the second call (after), it'll be already in place.
Expand All @@ -274,27 +268,40 @@ export class Vue implements Integration {
} else {
vm.$once(`hook:${hook}`, () => {
if (this._rootSpan) {
spans[op] = this._rootSpan.startChild({
spans[operation] = this._rootSpan.startChild({
description: `Vue <${name}>`,
op,
op: operation,
});
}
});
}
};

// Each compomnent has it's own scope, so all activities are only related to one of them
this._options.tracingOptions.hooks.forEach(hook => {
const handler = rootMount ? rootHandler.bind(this, hook) : childHandler.bind(this, hook);
const currentValue = vm.$options[hook];

if (Array.isArray(currentValue)) {
vm.$options[hook] = [handler, ...currentValue];
} else if (typeof currentValue === 'function') {
vm.$options[hook] = [handler, currentValue];
} else {
vm.$options[hook] = [handler];
this._options.tracingOptions.hooks.forEach(operation => {
// Retrieve corresponding hooks from Vue lifecycle.
// eg. mount => ['beforeMount', 'mounted']
const internalHooks = HOOKS[operation];

if (!internalHooks) {
logger.warn(`Unknown hook: ${operation}`);
return;
}

internalHooks.forEach(internalHook => {
const handler = rootMount
? rootHandler.bind(this, internalHook)
: childHandler.bind(this, internalHook, operation);
const currentValue = vm.$options[internalHook];

if (Array.isArray(currentValue)) {
vm.$options[internalHook] = [handler, ...currentValue];
} else if (typeof currentValue === 'function') {
vm.$options[internalHook] = [handler, currentValue];
} else {
vm.$options[internalHook] = [handler];
}
});
});
};

Expand Down