Skip to content

Commit

Permalink
feat(react): Add useProfiler hook (#2659)
Browse files Browse the repository at this point in the history
  • Loading branch information
AbhiPrasad committed Jun 9, 2020
1 parent 47b654c commit 026dfe9
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,7 @@
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
- [react] feat: Add @sentry/react package (#2631)
- [react] feat: Add Error Boundary component (#2647)
- [react] feat: Add useProfiler hook (#2659)

## 5.17.0

Expand Down
3 changes: 3 additions & 0 deletions packages/react/package.json
Expand Up @@ -28,6 +28,7 @@
},
"devDependencies": {
"@testing-library/react": "^10.0.6",
"@testing-library/react-hooks": "^3.3.0",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/react": "^16.9.35",
"jest": "^24.7.1",
Expand All @@ -36,9 +37,11 @@
"prettier-check": "^2.0.0",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-test-renderer": "^16.13.1",
"rimraf": "^2.6.3",
"tslint": "^5.16.0",
"tslint-react": "^5.0.0",
"tslint-react-hooks": "^2.2.2",
"typescript": "^3.5.1"
},
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/index.ts
@@ -1,4 +1,4 @@
export * from '@sentry/browser';

export { Profiler, withProfiler } from './profiler';
export { Profiler, withProfiler, useProfiler } from './profiler';
export { ErrorBoundary, withErrorBoundary } from './errorboundary';
23 changes: 22 additions & 1 deletion packages/react/src/profiler.tsx
Expand Up @@ -111,4 +111,25 @@ function withProfiler<P extends object>(WrappedComponent: React.ComponentType<P>
return Wrapped;
}

export { withProfiler, Profiler };
/**
*
* `useProfiler` is a React hook that profiles a React component.
*
* Requires React 16.8 or above.
* @param name displayName of component being profiled
*/
function useProfiler(name: string): void {
const activity = getInitActivity(name);

React.useEffect(() => {
afterNextFrame(() => {
const tracingIntegration = getCurrentHub().getIntegration(TRACING_GETTER);
if (tracingIntegration !== null) {
// tslint:disable-next-line:no-unsafe-any
(tracingIntegration as any).constructor.popActivity(activity);
}
});
}, []);
}

export { withProfiler, Profiler, useProfiler };
82 changes: 55 additions & 27 deletions packages/react/test/profiler.test.tsx
@@ -1,7 +1,8 @@
import { render } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import * as React from 'react';

import { UNKNOWN_COMPONENT, withProfiler } from '../src/profiler';
import { UNKNOWN_COMPONENT, useProfiler, withProfiler } from '../src/profiler';

const mockPushActivity = jest.fn().mockReturnValue(1);
const mockPopActivity = jest.fn();
Expand All @@ -25,46 +26,73 @@ jest.mock('@sentry/browser', () => ({
}));

describe('withProfiler', () => {
beforeEach(() => {
jest.useFakeTimers();
mockPushActivity.mockClear();
mockPopActivity.mockClear();
});

it('sets displayName properly', () => {
const TestComponent = () => <h1>Hello World</h1>;

const ProfiledComponent = withProfiler(TestComponent);
expect(ProfiledComponent.displayName).toBe('profiler(TestComponent)');
});

describe('Tracing Integration', () => {
beforeEach(() => {
jest.useFakeTimers();
mockPushActivity.mockClear();
mockPopActivity.mockClear();
});
it('popActivity() is called when unmounted', () => {
const ProfiledComponent = withProfiler(() => <h1>Hello World</h1>);

it('is called with popActivity() when unmounted', () => {
const ProfiledComponent = withProfiler(() => <h1>Hello World</h1>);
expect(mockPopActivity).toHaveBeenCalledTimes(0);
const profiler = render(<ProfiledComponent />);
profiler.unmount();

expect(mockPopActivity).toHaveBeenCalledTimes(0);
jest.runAllTimers();

const profiler = render(<ProfiledComponent />);
profiler.unmount();
expect(mockPopActivity).toHaveBeenCalledTimes(1);
expect(mockPopActivity).toHaveBeenLastCalledWith(1);
});

jest.runAllTimers();
it('pushActivity() is called when mounted', () => {
const ProfiledComponent = withProfiler(() => <h1>Testing</h1>);

expect(mockPopActivity).toHaveBeenCalledTimes(1);
expect(mockPopActivity).toHaveBeenLastCalledWith(1);
expect(mockPushActivity).toHaveBeenCalledTimes(0);
render(<ProfiledComponent />);
expect(mockPushActivity).toHaveBeenCalledTimes(1);
expect(mockPushActivity).toHaveBeenLastCalledWith(UNKNOWN_COMPONENT, {
description: `<${UNKNOWN_COMPONENT}>`,
op: 'react',
});
});
});

describe('useProfiler()', () => {
beforeEach(() => {
jest.useFakeTimers();
mockPushActivity.mockClear();
mockPopActivity.mockClear();
});

it('popActivity() is called when unmounted', () => {
// tslint:disable-next-line: no-void-expression
const profiler = renderHook(() => useProfiler('Example'));
expect(mockPopActivity).toHaveBeenCalledTimes(0);
profiler.unmount();

jest.runAllTimers();

expect(mockPopActivity).toHaveBeenCalled();
expect(mockPopActivity).toHaveBeenLastCalledWith(1);
});

describe('pushActivity()', () => {
it('is called when mounted', () => {
const ProfiledComponent = withProfiler(() => <h1>Testing</h1>);

expect(mockPushActivity).toHaveBeenCalledTimes(0);
render(<ProfiledComponent />);
expect(mockPushActivity).toHaveBeenCalledTimes(1);
expect(mockPushActivity).toHaveBeenLastCalledWith(UNKNOWN_COMPONENT, {
description: `<${UNKNOWN_COMPONENT}>`,
op: 'react',
});
});
it('pushActivity() is called when mounted', () => {
expect(mockPushActivity).toHaveBeenCalledTimes(0);
// tslint:disable-next-line: no-void-expression
const profiler = renderHook(() => useProfiler('Example'));
profiler.unmount();
expect(mockPushActivity).toHaveBeenCalledTimes(1);
expect(mockPushActivity).toHaveBeenLastCalledWith('Example', {
description: `<Example>`,
op: 'react',
});
});
});
2 changes: 1 addition & 1 deletion packages/react/tslint.json
@@ -1,5 +1,5 @@
{
"extends": ["@sentry/typescript/tslint", "tslint-react"],
"extends": ["@sentry/typescript/tslint", "tslint-react", "tslint-react-hooks"],
"rules": {
"no-implicit-dependencies": [
true,
Expand Down
77 changes: 74 additions & 3 deletions yarn.lock
Expand Up @@ -119,7 +119,7 @@
core-js-pure "^3.0.0"
regenerator-runtime "^0.13.4"

"@babel/runtime@^7.10.2", "@babel/runtime@^7.7.4":
"@babel/runtime@^7.10.2", "@babel/runtime@^7.5.4", "@babel/runtime@^7.7.4":
version "7.10.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839"
integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==
Expand Down Expand Up @@ -1195,6 +1195,14 @@
dom-accessibility-api "^0.4.4"
pretty-format "^25.5.0"

"@testing-library/react-hooks@^3.3.0":
version "3.3.0"
resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-3.3.0.tgz#dc217bfce8e7c34a99c811d73d23feef957b7c1d"
integrity sha512-rE9geI1+HJ6jqXkzzJ6abREbeud6bLF8OmF+Vyc7gBoPwZAEVBYjbC1up5nNoVfYBhO5HUwdD4u9mTehAUeiyw==
dependencies:
"@babel/runtime" "^7.5.4"
"@types/testing-library__react-hooks" "^3.0.0"

"@testing-library/react@^10.0.6":
version "10.0.6"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-10.0.6.tgz#e1e569135badb7367cc6ac9823e376eccb0280e0"
Expand Down Expand Up @@ -1453,6 +1461,13 @@
dependencies:
"@types/react" "*"

"@types/react-test-renderer@*":
version "16.9.2"
resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.2.tgz#e1c408831e8183e5ad748fdece02214a7c2ab6c5"
integrity sha512-4eJr1JFLIAlWhzDkBCkhrOIWOvOxcCAfQh+jiKg7l/nNZcCIL2MHl2dZhogIFKyHzedVWHaVP1Yydq/Ruu4agw==
dependencies:
"@types/react" "*"

"@types/react@*", "@types/react@^16.9.35":
version "16.9.35"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.35.tgz#a0830d172e8aadd9bd41709ba2281a3124bbd368"
Expand Down Expand Up @@ -1511,6 +1526,14 @@
dependencies:
pretty-format "^25.1.0"

"@types/testing-library__react-hooks@^3.0.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@types/testing-library__react-hooks/-/testing-library__react-hooks-3.2.0.tgz#52f3a109bef06080e3b1e3ae7ea1c014ce859897"
integrity sha512-dE8iMTuR5lzB+MqnxlzORlXzXyCL0EKfzH0w/lau20OpkHD37EaWjZDz0iNG8b71iEtxT4XKGmSKAGVEqk46mw==
dependencies:
"@types/react" "*"
"@types/react-test-renderer" "*"

"@types/testing-library__react@^10.0.1":
version "10.0.1"
resolved "https://registry.yarnpkg.com/@types/testing-library__react/-/testing-library__react-10.0.1.tgz#92bb4a02394bf44428e35f1da2970ed77f803593"
Expand Down Expand Up @@ -1787,11 +1810,32 @@ after@0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"

agent-base@4, agent-base@5, agent-base@6, agent-base@^4.3.0, agent-base@~4.2.0:
agent-base@4, agent-base@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
dependencies:
es6-promisify "^5.0.0"

agent-base@5:
version "5.1.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==

agent-base@6:
version "6.0.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.0.tgz#5d0101f19bbfaed39980b22ae866de153b93f09a"
integrity sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==
dependencies:
debug "4"

agent-base@~4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==
dependencies:
es6-promisify "^5.0.0"

agentkeepalive@^3.4.1:
version "3.5.2"
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.2.tgz#a113924dd3fa24a0bc3b78108c450c2abee00f67"
Expand Down Expand Up @@ -4509,6 +4553,18 @@ es-to-primitive@^1.1.1, es-to-primitive@^1.2.0:
is-date-object "^1.0.1"
is-symbol "^1.0.2"

es6-promise@^4.0.3:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==

es6-promisify@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
dependencies:
es6-promise "^4.0.3"

escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
Expand Down Expand Up @@ -9303,7 +9359,7 @@ react-dom@^16.0.0:
prop-types "^15.6.2"
scheduler "^0.19.1"

react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1:
react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
Expand All @@ -9313,6 +9369,16 @@ react-is@^16.8.4:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.4.tgz#90f336a68c3a29a096a3d648ab80e87ec61482a2"
integrity sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==

react-test-renderer@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1"
integrity sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ==
dependencies:
object-assign "^4.1.1"
prop-types "^15.6.2"
react-is "^16.8.6"
scheduler "^0.19.1"

react@^16.0.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
Expand Down Expand Up @@ -11034,6 +11100,11 @@ tslint-consistent-codestyle@^1.15.1:
tslib "^1.7.1"
tsutils "^2.29.0"

tslint-react-hooks@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/tslint-react-hooks/-/tslint-react-hooks-2.2.2.tgz#4dc9b3986196802d45c11cc0bf6319a8116fe2ed"
integrity sha512-gtwA14+WevNUtlBhvAD5Ukpxt2qMegYI7IDD8zN/3JXLksdLdEuU/T/oqlI1CtZhMJffqyNn+aqq2oUqUFXiNA==

tslint-react@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/tslint-react/-/tslint-react-5.0.0.tgz#d0ae644e8163bdd3e134012e9353094904e8dd44"
Expand Down

0 comments on commit 026dfe9

Please sign in to comment.