From 83cbebd7f8a0ae12f2b420e9095d4efff8d10d73 Mon Sep 17 00:00:00 2001 From: Hamid Date: Mon, 8 Jul 2019 15:35:27 +0200 Subject: [PATCH] feat: Initial react integration (#265) * feat(rum-react): initial react integration * chore(rum-react): run e2e tests * chore(rum): add history API e2e tests Minor refactoring * fix rum and rum-react tests * avoid changing the transaction type on reuse add lazy loading components test to rum-react * chore: fix react dependencies minor fixes * chore: refactor babel configs add build:main script TransactionService should check the transaction type onEnd Improve e2e tests * check options.canReuse before reusing transaction * address review comments --- .eslintrc.js | 5 +- .travis.yml | 12 + babel.node.js | 17 +- dev-utils/babel.js | 46 ++ dev-utils/run-script.js | 1 - dev-utils/webdriver.js | 134 ++++- package-lock.json | 568 +++++++++++++++++- package.json | 7 +- .../performance-monitoring.js | 5 + .../transaction-service.js | 11 +- .../src/performance-monitoring/transaction.js | 13 +- .../transaction-service.spec.js | 17 +- packages/rum-react/README.md | 8 + packages/rum-react/babel.config.js | 37 ++ packages/rum-react/karma.conf.js | 74 +++ packages/rum-react/package.json | 45 ++ packages/rum-react/src/get-apm-route.js | 43 ++ .../rum-react/src/get-with-transaction.js | 83 +++ packages/rum-react/src/index.js | 33 + .../rum.js => rum-react/test/e2e/index.js} | 13 +- .../rum-react/test/e2e/react/app.e2e-spec.js | 65 ++ .../{rum => rum-react}/test/e2e/react/app.jsx | 56 +- .../test/e2e/react/data.json | 0 .../test/e2e/react/index.html | 1 - .../test/e2e/react/main-component.jsx | 17 +- .../test/e2e/react/manual-component.jsx | 36 ++ .../test/e2e/react/topic-component.jsx | 62 ++ .../test/e2e/react/webpack.config.js | 26 +- .../test/specs/get-with-transaction.spec.js | 82 +++ packages/rum-react/wdio.conf.js | 28 + packages/rum/src/apm-base.js | 8 +- .../test/e2e/general-usecase/app.e2e-spec.js | 85 +-- packages/rum/test/e2e/general-usecase/app.js | 23 + packages/rum/test/e2e/logs-to-dom.js | 53 ++ .../test/e2e/manual-timing/app.e2e-spec.js | 59 +- packages/rum/test/e2e/react/app.e2e-spec.js | 109 ---- packages/rum/test/specs/apm-base.spec.js | 8 +- 37 files changed, 1590 insertions(+), 300 deletions(-) create mode 100644 dev-utils/babel.js create mode 100644 packages/rum-react/README.md create mode 100644 packages/rum-react/babel.config.js create mode 100644 packages/rum-react/karma.conf.js create mode 100644 packages/rum-react/package.json create mode 100644 packages/rum-react/src/get-apm-route.js create mode 100644 packages/rum-react/src/get-with-transaction.js create mode 100644 packages/rum-react/src/index.js rename packages/{rum/test/e2e/react/rum.js => rum-react/test/e2e/index.js} (81%) create mode 100644 packages/rum-react/test/e2e/react/app.e2e-spec.js rename packages/{rum => rum-react}/test/e2e/react/app.jsx (61%) rename packages/{rum => rum-react}/test/e2e/react/data.json (100%) rename packages/{rum => rum-react}/test/e2e/react/index.html (60%) rename packages/{rum => rum-react}/test/e2e/react/main-component.jsx (80%) create mode 100644 packages/rum-react/test/e2e/react/manual-component.jsx create mode 100644 packages/rum-react/test/e2e/react/topic-component.jsx rename packages/{rum => rum-react}/test/e2e/react/webpack.config.js (72%) create mode 100644 packages/rum-react/test/specs/get-with-transaction.spec.js create mode 100644 packages/rum-react/wdio.conf.js create mode 100644 packages/rum/test/e2e/logs-to-dom.js delete mode 100644 packages/rum/test/e2e/react/app.e2e-spec.js diff --git a/.eslintrc.js b/.eslintrc.js index 1e6b2448a..f5e9f5733 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,7 +19,7 @@ module.exports = { es6: true, browser: true }, - extends: ['plugin:prettier/recommended'], + extends: ['plugin:prettier/recommended', 'plugin:react/recommended'], parser: 'babel-eslint', plugins: ['standard', 'rulesdir'], rules: { @@ -31,6 +31,7 @@ module.exports = { { license: LICENSE_HEADER } - ] + ], + 'react/prop-types': 0 } } diff --git a/.travis.yml b/.travis.yml index b61bba84d..b35be8cbe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,10 @@ jobs: env: - STACK_VERSION=6.5.0 - SCOPE=@elastic/apm-rum + - stage: test + env: + - STACK_VERSION=6.5.0 + - SCOPE=@elastic/apm-rum-react - stage: test env: - STACK_VERSION=6.6.0 @@ -48,6 +52,10 @@ jobs: env: - STACK_VERSION=6.6.0 - SCOPE=@elastic/apm-rum + - stage: test + env: + - STACK_VERSION=6.6.0 + - SCOPE=@elastic/apm-rum-react - stage: test env: - STACK_VERSION=7.0.0 @@ -56,6 +64,10 @@ jobs: env: - STACK_VERSION=7.0.0 - SCOPE=@elastic/apm-rum + - stage: test + env: + - STACK_VERSION=7.0.0 + - SCOPE=@elastic/apm-rum-react - stage: test script: npm run build-docs name: 'Build Docs' diff --git a/babel.node.js b/babel.node.js index 909ac436b..c1bcb5762 100644 --- a/babel.node.js +++ b/babel.node.js @@ -23,20 +23,9 @@ * */ +const { getBabelConfig } = require('./dev-utils/babel') + module.exports = function(api) { api.cache(true) - return { - comments: false, - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: '8.0.0' - }, - loose: true - } - ] - ] - } + return getBabelConfig() } diff --git a/dev-utils/babel.js b/dev-utils/babel.js new file mode 100644 index 000000000..52fde9098 --- /dev/null +++ b/dev-utils/babel.js @@ -0,0 +1,46 @@ +/** + * MIT License + * + * Copyright (c) 2017-present, Elasticsearch BV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +function getBabelConfig() { + return { + comments: false, + presets: [ + [ + '@babel/preset-env', + { + targets: { + node: '8.0.0' + }, + loose: true + } + ] + ], + plugins: [] + } +} + +module.exports = { + getBabelConfig +} diff --git a/dev-utils/run-script.js b/dev-utils/run-script.js index f96b963d3..f4deabb81 100644 --- a/dev-utils/run-script.js +++ b/dev-utils/run-script.js @@ -30,7 +30,6 @@ const { runE2eTests: runE2eTestsUtils, buildE2eBundles: buildE2eBundlesUtils } = require('./test-utils') - const runAll = require('npm-run-all') const { startTestServers } = require('./test-servers') diff --git a/dev-utils/webdriver.js b/dev-utils/webdriver.js index 377d801d2..77f8c88ee 100644 --- a/dev-utils/webdriver.js +++ b/dev-utils/webdriver.js @@ -105,23 +105,32 @@ function isLogEntryATestFailure(entry, whitelist) { return result } -function getWebdriveBaseConfig(path) { +function getWebdriveBaseConfig( + path, + specs = './test/e2e/**/*.e2e-spec.js', + capabilities +) { const { tunnelIdentifier, username, accessKey } = getSauceConnectOptions() - /** - * Skip the ios platform on E2E tests because of script - * timeout issue in Appium - */ - const capabilities = getBrowserList() - .filter(({ platformName }) => platformName !== 'iOS') - .map(capability => ({ - tunnelIdentifier, - ...capability - })) + if (!capabilities) { + /** + * Skip the ios platform on E2E tests because of script + * timeout issue in Appium + */ + + capabilities = getBrowserList().filter( + ({ platformName }) => platformName !== 'iOS' + ) + } + + capabilities = capabilities.map(capability => ({ + tunnelIdentifier, + ...capability + })) return { runner: 'local', - specs: glob.sync(join(path, './test/e2e/**/*.e2e-spec.js')), + specs: glob.sync(join(path, specs)), maxInstancesPerCapability: 3, services: ['sauce'], user: username, @@ -158,6 +167,104 @@ function getWebdriveBaseConfig(path) { } } +function waitForApmServerCalls(errorCount = 0, transactionCount = 0) { + console.log( + `Waiting for minimum ${errorCount} Errors and ${transactionCount} Transactions.` + ) + const serverCalls = browser.executeAsync( + function(errorCount, transactionCount, done) { + var apmServerMock = window.elasticApm.serviceFactory.getService( + 'ApmServer' + ) + + function checkCalls() { + var serverCalls = apmServerMock.calls + + var validCalls = true + + if (errorCount) { + validCalls = + validCalls && + serverCalls.sendErrors && + serverCalls.sendErrors.length >= errorCount + } + if (transactionCount) { + validCalls = + validCalls && + serverCalls.sendTransactions && + serverCalls.sendTransactions.length >= transactionCount + } + + if (validCalls) { + console.log('calls', serverCalls) + var promises = [] + + if (serverCalls.sendErrors) { + promises = promises.concat( + serverCalls.sendErrors.map(function(s) { + return s.returnValue + }) + ) + } + if (serverCalls.sendTransactions) { + promises = promises.concat( + serverCalls.sendTransactions.map(function(s) { + return s.returnValue + }) + ) + } + + Promise.all(promises) + .then(function() { + function mapCall(c) { + return { args: c.args, mocked: c.mocked } + } + try { + var calls = { + sendErrors: serverCalls.sendErrors + ? serverCalls.sendErrors.map(mapCall) + : undefined, + sendTransactions: serverCalls.sendTransactions + ? serverCalls.sendTransactions.map(mapCall) + : undefined + } + done(calls) + } catch (e) { + throw e + } + }) + .catch(function(reason) { + console.log('reason', reason) + try { + done({ error: reason.message || JSON.stringify(reason) }) + } catch (e) { + done({ + error: 'Failed serializing rejection reason: ' + e.message + }) + } + }) + } + } + + checkCalls() + apmServerMock.subscription.subscribe(checkCalls) + }, + errorCount, + transactionCount + ) + + if (!serverCalls) { + throw new Error('serverCalls is undefined!') + } + + console.log(JSON.stringify(serverCalls, null, 2)) + if (serverCalls.error) { + fail(serverCalls.error) + } + + return serverCalls +} + module.exports = { allowSomeBrowserErrors: function allowSomeBrowserErrors(whitelist, done) { if (typeof done === 'function') { @@ -187,5 +294,6 @@ module.exports = { } }, isChrome, - getWebdriveBaseConfig + getWebdriveBaseConfig, + waitForApmServerCalls } diff --git a/package-lock.json b/package-lock.json index 409232f3f..0ba5d7fdb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -387,6 +387,15 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz", + "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, "@babel/plugin-syntax-json-strings": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", @@ -909,6 +918,23 @@ "uuid": "^3.1.0" } }, + "@elastic/apm-rum-react": { + "version": "file:packages/rum-react", + "requires": { + "@elastic/apm-rum": "file:packages/rum", + "hoist-non-react-statics": "^3.3.0" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", + "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", + "requires": { + "react-is": "^16.7.0" + } + } + } + }, "@lerna/add": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.14.0.tgz", @@ -2118,6 +2144,12 @@ "integrity": "sha512-vw3VyFPa9mlba6NZPBZC3q2Zrnkgy5xuCVI43/tTLX6umdYrYvcFtQUKi2zH3PjFZQ9XCxNM/NMrM9uk8TPOzg==", "dev": true }, + "@types/node": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.0.tgz", + "integrity": "sha512-Jrb/x3HT4PTJp6a4avhmJCDEVrPdqLfl3e8GGMbpkGGdwAV5UGlIs4vVEfsHHfylZVOKZWpOqmqFH8CbfOZ6kg==", + "dev": true + }, "@wdio/cli": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-5.7.2.tgz", @@ -2797,6 +2829,32 @@ "humanize-ms": "^1.2.1" } }, + "airbnb-prop-types": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.13.2.tgz", + "integrity": "sha512-2FN6DlHr6JCSxPPi25EnqGaXC4OC3/B3k1lCd6MMYrZ51/Gf/1qDfaR+JElzWa+Tl7cY2aYOlsYJGFeQyVHIeQ==", + "dev": true, + "requires": { + "array.prototype.find": "^2.0.4", + "function.prototype.name": "^1.1.0", + "has": "^1.0.3", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object.assign": "^4.1.0", + "object.entries": "^1.1.0", + "prop-types": "^15.7.2", + "prop-types-exact": "^1.2.0", + "react-is": "^16.8.6" + }, + "dependencies": { + "react-is": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", + "dev": true + } + } + }, "ajv": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", @@ -3270,6 +3328,16 @@ "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", "dev": true }, + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" + } + }, "array-map": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", @@ -3303,6 +3371,27 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "array.prototype.find": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.0.4.tgz", + "integrity": "sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" + } + }, + "array.prototype.flat": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz", + "integrity": "sha512-rVqIs330nLJvfC7JqYvEWwqVr5QjYF1ib02i3YJtR/fICO6527Tjpc/e4Mvmxh3GIePPreRXMdaGyC99YphWEw==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.10.0", + "function-bind": "^1.1.1" + } + }, "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", @@ -3775,6 +3864,12 @@ } } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4362,6 +4457,20 @@ "integrity": "sha512-YbulWHdfP99UfZ73NcUDlNJhEIDgm9Doq9GhpyXbF+7Aegi3CVV7qqMCKTTqJxlvEvnQBp9IA+dxsGN6xK/nSg==", "dev": true }, + "cheerio": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", + "dev": true, + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.1", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } + }, "chokidar": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.2.tgz", @@ -5855,12 +5964,30 @@ "randomfill": "^1.0.3" } }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, "css-value": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", "integrity": "sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo=", "dev": true }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -6214,6 +6341,12 @@ } } }, + "discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", + "dev": true + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -6235,12 +6368,47 @@ "void-elements": "^2.0.0" } }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", @@ -6462,6 +6630,78 @@ "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", "dev": true }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "enzyme": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.9.0.tgz", + "integrity": "sha512-JqxI2BRFHbmiP7/UFqvsjxTirWoM1HfeaJrmVSZ9a1EADKkZgdPcAuISPMpoUiHlac9J4dYt81MC5BBIrbJGMg==", + "dev": true, + "requires": { + "array.prototype.flat": "^1.2.1", + "cheerio": "^1.0.0-rc.2", + "function.prototype.name": "^1.1.0", + "has": "^1.0.3", + "html-element-map": "^1.0.0", + "is-boolean-object": "^1.0.0", + "is-callable": "^1.1.4", + "is-number-object": "^1.0.3", + "is-regex": "^1.0.4", + "is-string": "^1.0.4", + "is-subset": "^0.1.1", + "lodash.escape": "^4.0.1", + "lodash.isequal": "^4.5.0", + "object-inspect": "^1.6.0", + "object-is": "^1.0.1", + "object.assign": "^4.1.0", + "object.entries": "^1.0.4", + "object.values": "^1.0.4", + "raf": "^3.4.0", + "rst-selector-parser": "^2.2.3", + "string.prototype.trim": "^1.1.2" + } + }, + "enzyme-adapter-react-16": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.12.1.tgz", + "integrity": "sha512-GB61gvY97XvrA6qljExGY+lgI6BBwz+ASLaRKct9VQ3ozu0EraqcNn3CcrUckSGIqFGa1+CxO5gj5is5t3lwrw==", + "dev": true, + "requires": { + "enzyme-adapter-utils": "^1.11.0", + "object.assign": "^4.1.0", + "object.values": "^1.1.0", + "prop-types": "^15.7.2", + "react-is": "^16.8.6", + "react-test-renderer": "^16.0.0-0", + "semver": "^5.6.0" + }, + "dependencies": { + "react-is": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", + "dev": true + } + } + }, + "enzyme-adapter-utils": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.11.0.tgz", + "integrity": "sha512-0VZeoE9MNx+QjTfsjmO1Mo+lMfunucYB4wt5ficU85WB/LoetTJrbuujmHP3PJx6pSoaAuLA+Mq877x4LoxdNg==", + "dev": true, + "requires": { + "airbnb-prop-types": "^2.12.0", + "function.prototype.name": "^1.1.0", + "object.assign": "^4.1.0", + "object.fromentries": "^2.0.0", + "prop-types": "^15.7.2", + "semver": "^5.6.0" + } + }, "err-code": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", @@ -6671,6 +6911,41 @@ "prettier-linter-helpers": "^1.0.0" } }, + "eslint-plugin-react": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.13.0.tgz", + "integrity": "sha512-uA5LrHylu8lW/eAH3bEQe9YdzpPaFd9yAJTwTi/i/BKTD7j6aQMKVAdGM/ML72zD6womuSK7EiGtMKuK06lWjQ==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.1.0", + "object.fromentries": "^2.0.0", + "prop-types": "^15.7.2", + "resolve": "^1.10.1" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "resolve": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", + "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, "eslint-plugin-rulesdir": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-rulesdir/-/eslint-plugin-rulesdir-0.1.0.tgz", @@ -8599,6 +8874,17 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "function.prototype.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.0.tgz", + "integrity": "sha512-Bs0VRrTz4ghD8pTmbJQD1mZ8A/mN0ur/jGz+A6FBxPDUPkm1tNfF6bhTYPA7i7aF4lZJVr+OXTNNrnnIl58Wfg==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "is-callable": "^1.1.3" + } + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -9543,6 +9829,50 @@ "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, + "html-element-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.0.1.tgz", + "integrity": "sha512-BZSfdEm6n706/lBfXKWa4frZRZcT5k1cOusw95ijZsHlI+GdgY0v95h6IzO3iIDf2ROwq570YTwqNPqHcNMozw==", + "dev": true, + "requires": { + "array-filter": "^1.0.0" + }, + "dependencies": { + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "dev": true + } + } + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", + "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "http-cache-semantics": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", @@ -10044,6 +10374,12 @@ "binary-extensions": "^1.0.0" } }, + "is-boolean-object": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.0.tgz", + "integrity": "sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M=", + "dev": true + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -10153,6 +10489,12 @@ "kind-of": "^3.0.2" } }, + "is-number-object": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.3.tgz", + "integrity": "sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=", + "dev": true + }, "is-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", @@ -10251,6 +10593,18 @@ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-string": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.4.tgz", + "integrity": "sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ=", + "dev": true + }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", + "dev": true + }, "is-symbol": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", @@ -10621,6 +10975,15 @@ "verror": "1.10.0" } }, + "jsx-ast-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.1.0.tgz", + "integrity": "sha512-yDGDG2DS4JcqhA6blsuYbtsT09xL8AoLuUR2Gb5exrw7UEM19sBcOTq+YBBhrNbl0PUC4R4LnFu+dHg2HKeVvA==", + "dev": true, + "requires": { + "array-includes": "^3.0.3" + } + }, "karma": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/karma/-/karma-4.1.0.tgz", @@ -11242,6 +11605,12 @@ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, + "lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", + "dev": true + }, "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -11254,6 +11623,12 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, "lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", @@ -11813,6 +12188,12 @@ "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", "dev": true }, + "moo": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz", + "integrity": "sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw==", + "dev": true + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -11909,6 +12290,19 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "nearley": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.16.0.tgz", + "integrity": "sha512-Tr9XD3Vt/EujXbZBv6UAHYoLUSMQAxSsTnm9K3koXzjzNWY195NqALeyrzLZBKzAkL3gl92BcSogqrHjD8QuUg==", + "dev": true, + "requires": { + "commander": "^2.19.0", + "moo": "^0.4.3", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6", + "semver": "^5.4.1" + } + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", @@ -12264,6 +12658,15 @@ "set-blocking": "~2.0.0" } }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, "null-check": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", @@ -12316,6 +12719,18 @@ } } }, + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true + }, + "object-is": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", + "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=", + "dev": true + }, "object-keys": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", @@ -12339,6 +12754,42 @@ } } }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.fromentries": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", + "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.11.0", + "function-bind": "^1.1.1", + "has": "^1.0.1" + } + }, "object.getownpropertydescriptors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", @@ -12366,6 +12817,18 @@ } } }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, "octokit-pagination-methods": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz", @@ -12736,6 +13199,15 @@ "protocols": "^1.4.0" } }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "parseqs": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", @@ -13119,6 +13591,17 @@ "react-is": "^16.8.1" } }, + "prop-types-exact": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz", + "integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==", + "dev": true, + "requires": { + "has": "^1.0.3", + "object.assign": "^4.1.0", + "reflect.ownkeys": "^0.2.0" + } + }, "property-expr": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-1.5.1.tgz", @@ -13290,6 +13773,31 @@ "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", "dev": true }, + "raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dev": true, + "requires": { + "performance-now": "^2.1.0" + } + }, + "railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", + "dev": true + }, + "randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dev": true, + "requires": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + } + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -13383,8 +13891,7 @@ "react-is": { "version": "16.8.6", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", - "dev": true + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" }, "react-router": { "version": "4.3.1", @@ -13432,6 +13939,36 @@ "warning": "^4.0.1" } }, + "react-test-renderer": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.6.tgz", + "integrity": "sha512-H2srzU5IWYT6cZXof6AhUcx/wEyJddQ8l7cLM/F7gDXYyPr4oq+vCIxJYXVGhId1J706sqziAjuOEjyNkfgoEw==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "react-is": "^16.8.6", + "scheduler": "^0.13.6" + }, + "dependencies": { + "react-is": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", + "dev": true + }, + "scheduler": { + "version": "0.13.6", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz", + "integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + } + } + }, "read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -13872,6 +14409,12 @@ "strip-indent": "^1.0.1" } }, + "reflect.ownkeys": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", + "integrity": "sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=", + "dev": true + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", @@ -14141,6 +14684,16 @@ "inherits": "^2.0.1" } }, + "rst-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", + "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", + "dev": true, + "requires": { + "lodash.flattendeep": "^4.4.0", + "nearley": "^2.7.10" + } + }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", @@ -15097,6 +15650,17 @@ "function-bind": "^1.0.2" } }, + "string.prototype.trim": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", + "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.0", + "function-bind": "^1.0.2" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", diff --git a/package.json b/package.json index 53808dac5..f3d5322f2 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "devDependencies": { "@babel/cli": "^7.4.3", "@babel/core": "^7.2.0", + "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-transform-destructuring": "^7.3.2", "@babel/polyfill": "^7.2.5", "@babel/preset-env": "^7.2.0", @@ -58,10 +59,13 @@ "babel-plugin-istanbul": "^5.1.4", "benchmark": "^2.1.4", "bundlesize": "^0.17.2", + "enzyme": "^3.9.0", + "enzyme-adapter-react-16": "^1.12.1", "es6-promise": "^4.2.4", "eslint": "^5.12.0", "eslint-config-prettier": "^4.0.0", "eslint-plugin-prettier": "^3.0.1", + "eslint-plugin-react": "^7.13.0", "eslint-plugin-rulesdir": "^0.1.0", "eslint-plugin-standard": "^4.0.0", "express": "^4.16.2", @@ -105,6 +109,7 @@ }, "dependencies": { "@elastic/apm-rum": "file:packages/rum", - "@elastic/apm-rum-core": "file:packages/rum-core" + "@elastic/apm-rum-core": "file:packages/rum-core", + "@elastic/apm-rum-react": "file:packages/rum-react" } } diff --git a/packages/rum-core/src/performance-monitoring/performance-monitoring.js b/packages/rum-core/src/performance-monitoring/performance-monitoring.js index ffa97b3da..6c301cdc1 100644 --- a/packages/rum-core/src/performance-monitoring/performance-monitoring.js +++ b/packages/rum-core/src/performance-monitoring/performance-monitoring.js @@ -62,6 +62,11 @@ class PerformanceMonitoring { const payload = this.createTransactionPayload(tr) if (payload) { this._apmServer.addTransaction(payload) + } else if (__DEV__) { + this._logginService.debug( + 'Could not create a payload from the Transaction', + tr + ) } }) diff --git a/packages/rum-core/src/performance-monitoring/transaction-service.js b/packages/rum-core/src/performance-monitoring/transaction-service.js index 77cdf6da4..cddf8dc91 100644 --- a/packages/rum-core/src/performance-monitoring/transaction-service.js +++ b/packages/rum-core/src/performance-monitoring/transaction-service.js @@ -129,7 +129,7 @@ class TransactionService { if (!tr) { tr = this.createTransaction(name, type, perfOptions) - } else if (tr.canReuse()) { + } else if (tr.canReuse() && perfOptions.canReuse) { /* * perfOptions could also have `canReuse:true` in which case we * allow a redefinition until there's a call that doesn't have that @@ -144,7 +144,12 @@ class TransactionService { perfOptions ) } - tr.redefine(name, type, perfOptions) + /** + * We want to keep the type in it's original value, therefore, + * passing undefined as type. For example, in the case of a page-load + * we want to keep the type but redefine the name to the first route. + */ + tr.redefine(name, undefined, perfOptions) } else { if (__DEV__) { this._logger.debug('Ending old transaction', tr) @@ -183,7 +188,7 @@ class TransactionService { if (this.shouldIgnoreTransaction(tr.name)) { return } - if (type === PAGE_LOAD) { + if (tr.type === PAGE_LOAD) { /** * Setting the pageLoadTransactionName via configService.setConfig after * transaction has started should also reflect the correct name. diff --git a/packages/rum-core/src/performance-monitoring/transaction.js b/packages/rum-core/src/performance-monitoring/transaction.js index 88e534387..bef328eb2 100644 --- a/packages/rum-core/src/performance-monitoring/transaction.js +++ b/packages/rum-core/src/performance-monitoring/transaction.js @@ -73,9 +73,16 @@ class Transaction extends SpanBase { } redefine(name, type, options) { - this.name = name - this.type = type - this.options = extend(this.options, options) + if (name) { + this.name = name + } + if (type) { + this.type = type + } + + if (options) { + this.options = extend(this.options, options) + } } startSpan(name, type, options) { diff --git a/packages/rum-core/test/performance-monitoring/transaction-service.spec.js b/packages/rum-core/test/performance-monitoring/transaction-service.spec.js index d104e92ea..a1f10d4ab 100644 --- a/packages/rum-core/test/performance-monitoring/transaction-service.spec.js +++ b/packages/rum-core/test/performance-monitoring/transaction-service.spec.js @@ -94,14 +94,16 @@ describe('TransactionService', function() { expect(result.onEnd).toHaveBeenCalled() }) - it('should create a zone transaction on the first span', function() { + it('should create a reusable transaction on the first span', function() { config.set('active', true) transactionService = new TransactionService(logger, config) transactionService.startSpan('testSpan', 'testtype') var trans = transactionService.getCurrentTransaction() expect(trans.name).toBe('Unknown') - transactionService.startTransaction('transaction', 'transaction') + transactionService.startTransaction('transaction', 'transaction', { + canReuse: true + }) expect(trans.name).toBe('transaction') }) @@ -138,13 +140,16 @@ describe('TransactionService', function() { it('should reuse Transaction', function() { transactionService = new TransactionService(logger, config) - const zoneTr = new Transaction('test-name', 'test-type', { + const reusableTr = new Transaction('test-name', 'test-type', { + canReuse: true + }) + transactionService.setCurrentTransaction(reusableTr) + const pageLoadTr = transactionService.startTransaction(name, 'page-load', { canReuse: true }) - transactionService.setCurrentTransaction(zoneTr) - const pageLoadTr = sendPageLoadMetrics('new tr') + pageLoadTr.detectFinish() - expect(pageLoadTr).toBe(zoneTr) + expect(pageLoadTr).toBe(reusableTr) }) it('should contain agent marks in page load transaction', function() { diff --git a/packages/rum-react/README.md b/packages/rum-react/README.md new file mode 100644 index 000000000..c5ff6f565 --- /dev/null +++ b/packages/rum-react/README.md @@ -0,0 +1,8 @@ +# `rum-react` + +> TODO: description + +## Usage + +> TODO: DEMONSTRATE API + diff --git a/packages/rum-react/babel.config.js b/packages/rum-react/babel.config.js new file mode 100644 index 000000000..857b7549d --- /dev/null +++ b/packages/rum-react/babel.config.js @@ -0,0 +1,37 @@ +/** + * MIT License + * + * Copyright (c) 2017-present, Elasticsearch BV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +const { getBabelConfig } = require('../../dev-utils/babel') + +module.exports = function(api) { + api.cache(true) + let config = getBabelConfig() + config.presets.push(['@babel/preset-react']) + config.plugins = config.plugins.concat([ + '@babel/plugin-transform-destructuring', + '@babel/plugin-syntax-dynamic-import' + ]) + return config +} diff --git a/packages/rum-react/karma.conf.js b/packages/rum-react/karma.conf.js new file mode 100644 index 000000000..e48041c2d --- /dev/null +++ b/packages/rum-react/karma.conf.js @@ -0,0 +1,74 @@ +/** + * MIT License + * + * Copyright (c) 2017-present, Elasticsearch BV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +const { baseConfig, prepareConfig } = require('../../dev-utils/karma.js') +const { getGlobalConfig } = require('../../dev-utils/test-config') +const { EnvironmentPlugin } = require('webpack') +const { getWebpackEnv } = require('../../dev-utils/test-config') + +module.exports = function(config) { + config.set(baseConfig) + config.webpack = { + devtool: 'source-map', + mode: 'development', + module: { + rules: [ + { + test: /.jsx?$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: [ + [ + '@babel/preset-env', + { + targets: { + ie: '11' + }, + useBuiltIns: false, + modules: 'umd' + } + ], + ['@babel/preset-react'] + ], + plugins: ['@babel/plugin-transform-destructuring'] + } + } + } + ] + }, + plugins: [new EnvironmentPlugin(getWebpackEnv())], + resolve: { + extensions: ['.js', '.jsx'] + } + } + + const testConfig = getGlobalConfig() + console.log('Custom Test Config:', testConfig) + config.set(testConfig) + const cfg = prepareConfig(config) + config.set(cfg) +} diff --git a/packages/rum-react/package.json b/packages/rum-react/package.json new file mode 100644 index 000000000..339f25168 --- /dev/null +++ b/packages/rum-react/package.json @@ -0,0 +1,45 @@ +{ + "name": "@elastic/apm-rum-react", + "version": "0.0.0", + "description": "Elastic APM Real User Monitoring for React applications", + "author": "Hamid ", + "homepage": "https://www.elastic.co/guide/en/apm/agent/js-base/current/index.html", + "license": "MIT", + "main": "dist/lib/index.js", + "module": "src/index.js", + "files": [ + "src", + "dist" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/elastic/apm-agent-rum-js.git" + }, + "scripts": { + "prepublishOnly": "npm run build", + "script": "node ../../dev-utils/run-script.js", + "build": "webpack && npm run build:main", + "build:dev": "webpack -w", + "build:main": "npx babel src -d dist/lib --config-file ./babel.config.js", + "build:e2e": "npm run script buildE2eBundles packages/rum-react/test/e2e", + "test:unit": "npm run script runUnitTests false packages/rum-react", + "test:e2e:supported": "npm run script runE2eTests packages/rum-react/wdio.conf.js", + "test:sauce": "npm run build:e2e && npm run script runSauceTests true packages/rum-react test:unit test:e2e:supported", + "karma": "karma start", + "test": "npm run test:sauce" + }, + "bugs": { + "url": "https://github.com/elastic/apm-agent-rum-js/issues" + }, + "dependencies": { + "hoist-non-react-statics": "^3.3.0", + "@elastic/apm-rum": "file:../rum" + }, + "peerDependencies": { + "react": "^16.2.0", + "react-router-dom": "^4.2.2" + } +} \ No newline at end of file diff --git a/packages/rum-react/src/get-apm-route.js b/packages/rum-react/src/get-apm-route.js new file mode 100644 index 000000000..ebf54efd2 --- /dev/null +++ b/packages/rum-react/src/get-apm-route.js @@ -0,0 +1,43 @@ +/** + * MIT License + * + * Copyright (c) 2017-present, Elasticsearch BV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +import React from 'react' +import { Route } from 'react-router-dom' +import { getWithTransaction } from './get-with-transaction' + +function getApmRoute(apm) { + const withTransaction = getWithTransaction(apm) + return class ApmRoute extends React.Component { + constructor(props) { + super(props) + const { path, component: Component } = this.props + this.ApmComponent = withTransaction(path, 'route-change')(Component) + } + render() { + return + } + } +} +export { getApmRoute } diff --git a/packages/rum-react/src/get-with-transaction.js b/packages/rum-react/src/get-with-transaction.js new file mode 100644 index 000000000..348f7dc6d --- /dev/null +++ b/packages/rum-react/src/get-with-transaction.js @@ -0,0 +1,83 @@ +/** + * MIT License + * + * Copyright (c) 2017-present, Elasticsearch BV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +/* + Usage: + - Pure function: `withTransaction('name','route-change')(Component)` + - As a decorator: `@withTransaction('name','route-change')` + */ + +import React from 'react' +import hoistStatics from 'hoist-non-react-statics' + +function getWithTransaction(apm) { + return function withTransaction(name, type) { + return function(WrappedComponent) { + class ApmComponent extends React.Component { + constructor(props) { + super(props) + /** + * We need to start the transaction in constructor because otherwise, + * we won't be able to capture what happens in componentDidMount of child components. + */ + this.transaction = apm.startTransaction(name, type, { + canReuse: true + }) + } + + componentDidMount() { + if (this.transaction) { + this.transaction.detectFinish() + } + } + + componentWillUnmount() { + /** + * It is possible that the transaction has ended before this unmount event, + * in that case this is a noop. + */ + if (this.transaction) { + this.transaction.detectFinish() + } + } + + render() { + // todo: should we pass the transaction down (could use react context provider instead) + return ( + + ) + } + } + + ApmComponent.displayName = `withTransaction(${WrappedComponent.displayName || + WrappedComponent.name})` + ApmComponent.WrappedComponent = WrappedComponent + + return hoistStatics(ApmComponent, WrappedComponent) + } + } +} + +export { getWithTransaction } diff --git a/packages/rum-react/src/index.js b/packages/rum-react/src/index.js new file mode 100644 index 000000000..97cd4a0b7 --- /dev/null +++ b/packages/rum-react/src/index.js @@ -0,0 +1,33 @@ +/** + * MIT License + * + * Copyright (c) 2017-present, Elasticsearch BV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +import { apm } from '@elastic/apm-rum' +import { getWithTransaction } from './get-with-transaction' +import { getApmRoute } from './get-apm-route' + +const withTransaction = getWithTransaction(apm) +const ApmRoute = getApmRoute(apm) + +export { withTransaction, ApmRoute } diff --git a/packages/rum/test/e2e/react/rum.js b/packages/rum-react/test/e2e/index.js similarity index 81% rename from packages/rum/test/e2e/react/rum.js rename to packages/rum-react/test/e2e/index.js index 1d20f32d4..5435cfd5e 100644 --- a/packages/rum/test/e2e/react/rum.js +++ b/packages/rum-react/test/e2e/index.js @@ -23,15 +23,6 @@ * */ -import createApmBase from '../' +import createApmBase from '../../../rum/test/e2e' -var apm = createApmBase({ - debug: true, - serviceName: 'apm-agent-rum-test-e2e-react', - serviceVersion: '0.0.1', - sendPageLoadTransaction: false -}) - -apm.setInitialPageLoadName('react-initial-page-load') - -export { apm } +export default createApmBase diff --git a/packages/rum-react/test/e2e/react/app.e2e-spec.js b/packages/rum-react/test/e2e/react/app.e2e-spec.js new file mode 100644 index 000000000..60f689d65 --- /dev/null +++ b/packages/rum-react/test/e2e/react/app.e2e-spec.js @@ -0,0 +1,65 @@ +/** + * MIT License + * + * Copyright (c) 2017-present, Elasticsearch BV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +const { + allowSomeBrowserErrors, + waitForApmServerCalls +} = require('../../../../../dev-utils/webdriver') + +describe('ReactApp', function() { + it('should run the react app', function() { + browser.url('/test/e2e/react/') + browser.waitUntil( + () => { + return $('#test-element').getText() === 'Passed' + }, + 5000, + 'expected element #test-element' + ) + + const serverCalls = waitForApmServerCalls(0, 1) + + expect(serverCalls.sendTransactions.length).toBe(1) + + var transaction = serverCalls.sendTransactions[0].args[0][0] + expect(transaction.type).toBe('page-load') + expect(transaction.name).toBe('/home') + expect(transaction.spans.length).toBeGreaterThan(1) + + const spanNames = [ + 'Requesting and receiving the document', + 'Parsing the document, executing sync. scripts', + 'GET /test/e2e/react/data.json', + 'Render' + ] + var foundSpans = transaction.spans.filter(span => { + return spanNames.indexOf(span.name) > -1 + }) + + expect(foundSpans.length).toBeGreaterThanOrEqual(4) + + return allowSomeBrowserErrors() + }) +}) diff --git a/packages/rum/test/e2e/react/app.jsx b/packages/rum-react/test/e2e/react/app.jsx similarity index 61% rename from packages/rum/test/e2e/react/app.jsx rename to packages/rum-react/test/e2e/react/app.jsx index 4a9402dd5..1e6ca543b 100644 --- a/packages/rum/test/e2e/react/app.jsx +++ b/packages/rum-react/test/e2e/react/app.jsx @@ -25,18 +25,34 @@ import '@babel/polyfill' import 'whatwg-fetch' -import React from 'react' +import React, { Suspense, lazy } from 'react' +const ManualComponent = lazy(() => import('./manual-component.jsx')) import ReactDOM from 'react-dom' -import { BrowserRouter as Router, Route, Link } from 'react-router-dom' +import { + BrowserRouter as Router, + Route, + Link, + Redirect +} from 'react-router-dom' import { withRouter } from 'react-router' import MainComponent from './main-component.jsx' +import TopicComponent from './topic-component' -import { apm } from './rum' +import { ApmRoute } from '../../../src' +import createApmBase from '../' +const apm = createApmBase({ + debug: true, + serverUrl: 'http://localhost:8200', + serviceName: 'apm-agent-rum-test-e2e-react', + serviceVersion: '0.0.1' +}) -var tr = apm.startTransaction('App Load', 'page-load') -tr.isHardNavigation = true +var tr = apm.getCurrentTransaction() class App extends React.Component { + constructor(props) { + super(props) + } render() { return (
@@ -48,6 +64,9 @@ class App extends React.Component {
  • About
  • +
  • + Manual +
  • Topics
  • @@ -57,10 +76,31 @@ class App extends React.Component {
    - + ( + + )} + /> + - - + + + { + return ( + Loading...
    }> + + + ) + }} + />
    Passed
    diff --git a/packages/rum/test/e2e/react/data.json b/packages/rum-react/test/e2e/react/data.json similarity index 100% rename from packages/rum/test/e2e/react/data.json rename to packages/rum-react/test/e2e/react/data.json diff --git a/packages/rum/test/e2e/react/index.html b/packages/rum-react/test/e2e/react/index.html similarity index 60% rename from packages/rum/test/e2e/react/index.html rename to packages/rum-react/test/e2e/react/index.html index 07b7ba464..7954c805a 100644 --- a/packages/rum/test/e2e/react/index.html +++ b/packages/rum-react/test/e2e/react/index.html @@ -4,7 +4,6 @@ React test - diff --git a/packages/rum/test/e2e/react/main-component.jsx b/packages/rum-react/test/e2e/react/main-component.jsx similarity index 80% rename from packages/rum/test/e2e/react/main-component.jsx rename to packages/rum-react/test/e2e/react/main-component.jsx index 414c02c21..da19c2266 100644 --- a/packages/rum/test/e2e/react/main-component.jsx +++ b/packages/rum-react/test/e2e/react/main-component.jsx @@ -25,8 +25,6 @@ import React from 'react' -import { apm } from './rum' - class MainComponent extends React.Component { constructor(props, state) { super(props, state) @@ -35,23 +33,26 @@ class MainComponent extends React.Component { userName: '', path } - this.transaction = apm.startTransaction('Main - ' + path, 'route-change') + // this.transaction = apm.startTransaction('Main - ' + path, 'route-change') + // this.transaction = apm.getCurrentTransaction() + this.transaction = this.props.transaction } componentDidMount() { this.fetchData() - this.transaction.detectFinish() + // this.transaction.detectFinish() } fetchData() { var url = '/test/e2e/react/data.json' + const transaction = this.transaction fetch(url) .then(resp => { - var tid = this.transaction.addTask() - var span = this.transaction.startSpan('Timeout span') + var tid = transaction && transaction.addTask() + var span = transaction && transaction.startSpan('Timeout span') setTimeout(() => { - span.end() - this.transaction.removeTask(tid) + span && span.end() + transaction && transaction.removeTask(tid) }, 500) return resp.json() }) diff --git a/packages/rum-react/test/e2e/react/manual-component.jsx b/packages/rum-react/test/e2e/react/manual-component.jsx new file mode 100644 index 000000000..ed88a2850 --- /dev/null +++ b/packages/rum-react/test/e2e/react/manual-component.jsx @@ -0,0 +1,36 @@ +import React from 'react' +import { withTransaction } from '../../../src'; + + +class ManualComponent extends React.Component { + constructor(props, state) { + super(props, state) + this.state = { + userName: '' + } + } + + componentDidMount() { + this.fetchData() + } + fetchData() { + var url = '/test/e2e/react/data.json' + fetch(url) + .then(resp => { + return resp.json() + }) + .then(data => { + this.setState({ userName: data.userName }) + }) + } + render() { + return ( +
    + Manual +
    + ) + } +} + + +export default withTransaction('ManualComponent', 'component')(ManualComponent) \ No newline at end of file diff --git a/packages/rum-react/test/e2e/react/topic-component.jsx b/packages/rum-react/test/e2e/react/topic-component.jsx new file mode 100644 index 000000000..a13663903 --- /dev/null +++ b/packages/rum-react/test/e2e/react/topic-component.jsx @@ -0,0 +1,62 @@ +/** + * MIT License + * + * Copyright (c) 2017-present, Elasticsearch BV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +import React from 'react' + +class TopicComponent extends React.Component { + constructor(props, state) { + super(props, state) + this.state = { + userName: '' + } + } + + componentDidMount() { + this.fetchData() + } + fetchData() { + var url = '/test/e2e/react/data.json' + fetch(url) + .then(resp => { + return resp.json() + }) + .then(data => { + this.setState({ userName: data.userName }) + }) + } + render() { + return ( +
    +

    + {this.props.match.path} +

    + {this.state.userName} +
    + ) + } +} + + +export default TopicComponent \ No newline at end of file diff --git a/packages/rum/test/e2e/react/webpack.config.js b/packages/rum-react/test/e2e/react/webpack.config.js similarity index 72% rename from packages/rum/test/e2e/react/webpack.config.js rename to packages/rum-react/test/e2e/react/webpack.config.js index 1303255e3..e09e74f57 100644 --- a/packages/rum/test/e2e/react/webpack.config.js +++ b/packages/rum-react/test/e2e/react/webpack.config.js @@ -31,7 +31,7 @@ module.exports = { entry: path.resolve(__dirname, './app.jsx'), output: { path: __dirname, filename: 'app.e2e-bundle.js' }, devtool: 'source-map', - mode: 'production', + mode: 'development', performance: { hints: false }, @@ -39,11 +39,31 @@ module.exports = { rules: [ { test: /.jsx?$/, + exclude: /node_modules/, use: { - loader: 'babel-loader' + loader: 'babel-loader', + options: { + presets: [ + [ + '@babel/preset-env', + { + targets: { + ie: '11' + }, + useBuiltIns: false, + modules: 'umd' + } + ], + ['@babel/preset-react'] + ], + plugins: ['@babel/plugin-transform-destructuring'] + } } } ] }, - plugins: [new EnvironmentPlugin(getWebpackEnv())] + plugins: [new EnvironmentPlugin(getWebpackEnv())], + resolve: { + extensions: ['.js', '.jsx'] + } } diff --git a/packages/rum-react/test/specs/get-with-transaction.spec.js b/packages/rum-react/test/specs/get-with-transaction.spec.js new file mode 100644 index 000000000..9e252e701 --- /dev/null +++ b/packages/rum-react/test/specs/get-with-transaction.spec.js @@ -0,0 +1,82 @@ +/** + * MIT License + * + * Copyright (c) 2017-present, Elasticsearch BV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +import Enzyme from 'enzyme' +import Adapter from 'enzyme-adapter-react-16' + +Enzyme.configure({ adapter: new Adapter() }) + +import { getWithTransaction } from '../../src/get-with-transaction' + +import { ApmBase } from '@elastic/apm-rum' +import { createServiceFactory } from '@elastic/apm-rum-core' +import React from 'react' +import { render } from 'enzyme' + +function TestComponent(apm) { + const withTransaction = getWithTransaction(apm) + function Component(props) { + return

    Testing, {props.name}

    + } + const WrappedComponent = withTransaction('test-transaction', 'test-type')( + Component + ) + expect(typeof WrappedComponent).toBe('function') + const rendered = render() + expect(rendered.length).toBe(1) + var node = rendered[0] + expect(node.name).toBe('h1') + expect(node.type).toBe('tag') + expect(rendered.text()).toBe('Testing, withTransaction') +} + +describe('withTransaction', function() { + it('should work if apm is disabled or not initialized', function() { + TestComponent(new ApmBase(createServiceFactory(), true)) + TestComponent(new ApmBase(createServiceFactory(), false)) + }) + + it('should start transaction for components', function() { + const serviceFactory = createServiceFactory() + const transactionService = serviceFactory.getService('TransactionService') + + var apm = new ApmBase(serviceFactory, false) + apm.init({ + debug: true, + serverUrl: 'http://localhost:8200', + serviceName: 'apm-agent-rum-react-integration-unit-test', + sendPageLoadTransaction: false + }) + + spyOn(transactionService, 'startTransaction') + + TestComponent(apm) + expect(transactionService.startTransaction).toHaveBeenCalledWith( + 'test-transaction', + 'test-type', + { canReuse: true } + ) + }) +}) diff --git a/packages/rum-react/wdio.conf.js b/packages/rum-react/wdio.conf.js new file mode 100644 index 000000000..e40d691d3 --- /dev/null +++ b/packages/rum-react/wdio.conf.js @@ -0,0 +1,28 @@ +/** + * MIT License + * + * Copyright (c) 2017-present, Elasticsearch BV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +const { getWebdriveBaseConfig } = require('../../dev-utils/webdriver') + +exports.config = getWebdriveBaseConfig(__dirname) diff --git a/packages/rum/src/apm-base.js b/packages/rum/src/apm-base.js index 05d8ed8f6..ee1e1aa38 100644 --- a/packages/rum/src/apm-base.js +++ b/packages/rum/src/apm-base.js @@ -74,7 +74,9 @@ class ApmBase { * Name of the transaction is set in transaction service to * avoid duplicate the logic at multiple places */ - const tr = transactionService.startTransaction(undefined, 'page-load') + const tr = transactionService.startTransaction(undefined, 'page-load', { + canReuse: true + }) if (tr) { tr.addTask(pageLoadTaskId) @@ -171,12 +173,12 @@ class ApmBase { } } - startTransaction(name, type) { + startTransaction(name, type, options) { if (this.isEnabled()) { var transactionService = this.serviceFactory.getService( 'TransactionService' ) - return transactionService.startTransaction(name, type) + return transactionService.startTransaction(name, type, options) } } diff --git a/packages/rum/test/e2e/general-usecase/app.e2e-spec.js b/packages/rum/test/e2e/general-usecase/app.e2e-spec.js index 0fb4aa53c..952078173 100644 --- a/packages/rum/test/e2e/general-usecase/app.e2e-spec.js +++ b/packages/rum/test/e2e/general-usecase/app.e2e-spec.js @@ -22,7 +22,10 @@ * THE SOFTWARE. * */ -const { allowSomeBrowserErrors } = require('../../../../../dev-utils/webdriver') +const { + allowSomeBrowserErrors, + waitForApmServerCalls +} = require('../../../../../dev-utils/webdriver') describe('general-usercase', function() { it('should run the general usecase', function() { @@ -35,61 +38,8 @@ describe('general-usercase', function() { 'expected element #test-element' ) - const serverCalls = browser.executeAsync(function(done) { - var apmServerMock = window.elasticApm.serviceFactory.getService( - 'ApmServer' - ) + const serverCalls = waitForApmServerCalls(1, 1) - function checkCalls() { - var serverCalls = apmServerMock.calls - var validCalls = - serverCalls.sendErrors && - serverCalls.sendErrors.length && - serverCalls.sendTransactions && - serverCalls.sendTransactions.length - - if (validCalls) { - console.log('calls', serverCalls) - Promise.all([ - serverCalls.sendErrors[0].returnValue, - serverCalls.sendTransactions[0].returnValue - ]) - .then(function() { - function mapCall(c) { - return { args: c.args, mocked: c.mocked } - } - try { - var calls = { - sendErrors: serverCalls.sendErrors.map(mapCall), - sendTransactions: serverCalls.sendTransactions.map(mapCall) - } - done(calls) - } catch (e) { - throw e - } - }) - .catch(function(reason) { - console.log('reason', reason) - try { - done({ error: reason.message || JSON.stringify(reason) }) - } catch (e) { - done({ - error: 'Failed serializing rejection reason: ' + e.message - }) - } - }) - } - } - - checkCalls() - apmServerMock.subscription.subscribe(checkCalls) - }) - - expect(serverCalls).toBeTruthy() - console.log(JSON.stringify(serverCalls, null, 2)) - if (serverCalls.error) { - fail(serverCalls.error) - } expect(serverCalls.sendErrors.length).toBe(1) var errorPayload = serverCalls.sendErrors[0].args[0][0] expect( @@ -129,4 +79,29 @@ describe('general-usercase', function() { return allowSomeBrowserErrors(['timeout test error with a secret']) }) + + it('should capture history.pushState', function() { + /** + * The query string is only used to make url different to the previous test, + * Otherwise, both tests will run in the same window. + */ + + browser.url('/test/e2e/general-usecase/index.html?run=pushState#test-state') + browser.waitUntil( + () => { + return $('#test-element').getText() === 'Passed' + }, + 5000, + 'expected element #test-element' + ) + + const serverCalls = waitForApmServerCalls(0, 1) + expect(serverCalls.sendTransactions.length).toBe(1) + const transactionPayload = serverCalls.sendTransactions[0].args[0][0] + expect(transactionPayload.name).toBe('Push state title') + /** + * The actual spans are tested as part of the previous test. + */ + expect(transactionPayload.spans.length).toBeGreaterThanOrEqual(3) + }) }) diff --git a/packages/rum/test/e2e/general-usecase/app.js b/packages/rum/test/e2e/general-usecase/app.js index 07d6ec1a1..6e73e2ecc 100644 --- a/packages/rum/test/e2e/general-usecase/app.js +++ b/packages/rum/test/e2e/general-usecase/app.js @@ -111,4 +111,27 @@ generateError.tmp = 'tmp' testFetch(mockBackendUrl) +if (location.hash === '#test-state') { + const path = location.pathname + history.pushState( + { data: 'buffer-state' }, + 'buffer state', + path + '#buffer-state' + ) + history.pushState( + { data: 'test-state' }, + 'Push state title', + path + '#test-state' + ) + + /** + * There is a bug in Android 5.1 that prevents load event, + * if history.go(-1) is called before the load event. + */ + + window.addEventListener('load', function() { + history.go(-1) + }) +} + renderTestElement() diff --git a/packages/rum/test/e2e/logs-to-dom.js b/packages/rum/test/e2e/logs-to-dom.js new file mode 100644 index 000000000..4b592600b --- /dev/null +++ b/packages/rum/test/e2e/logs-to-dom.js @@ -0,0 +1,53 @@ +/** + * MIT License + * + * Copyright (c) 2017-present, Elasticsearch BV + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +;(function() { + function _patchDebugMethods() { + function patch(target, name) { + var orig = target[name] + target[name] = function() { + var debugElement = document.createElement('li') + var logs = [].slice.call(arguments) + debugElement.innerHTML = name + ': ' + JSON.stringify(logs) //.slice(0, 1000) + document.body.appendChild(debugElement) + orig && orig.apply(this, arguments) + } + } + patch(console, 'log') + patch(console, 'info') + patch(console, 'error') + patch(console, 'debug') + patch(console, 'warn') + patch(window, 'onerror') + + // Use this to scroll the logs into the view + // setInterval(function () { + // window.scrollTo(0, document.body.scrollHeight); + // }, 1000) + } + + _patchDebugMethods() + console.log('Patched debug methods!') +})() diff --git a/packages/rum/test/e2e/manual-timing/app.e2e-spec.js b/packages/rum/test/e2e/manual-timing/app.e2e-spec.js index a5348cce1..58944dad1 100644 --- a/packages/rum/test/e2e/manual-timing/app.e2e-spec.js +++ b/packages/rum/test/e2e/manual-timing/app.e2e-spec.js @@ -23,7 +23,10 @@ * */ -const { verifyNoBrowserErrors } = require('../../../../../dev-utils/webdriver') +const { + verifyNoBrowserErrors, + waitForApmServerCalls +} = require('../../../../../dev-utils/webdriver') describe('manual-timing', function() { it('should run manual timing', async function() { @@ -36,60 +39,8 @@ describe('manual-timing', function() { 'expected element #test-element' ) - const serverCalls = browser.executeAsync(function(done) { - var apmServerMock = window.elasticApm.serviceFactory.getService( - 'ApmServer' - ) + const serverCalls = waitForApmServerCalls(1, 1) - function checkCalls() { - var serverCalls = apmServerMock.calls - var validCalls = - serverCalls.sendErrors && - serverCalls.sendErrors.length && - serverCalls.sendTransactions && - serverCalls.sendTransactions.length - if (validCalls) { - console.log('calls', serverCalls) - Promise.all([ - serverCalls.sendErrors[0].returnValue, - serverCalls.sendTransactions[0].returnValue - ]) - .then(function() { - function mapCall(c) { - return { args: c.args, mocked: c.mocked } - } - try { - var calls = { - sendErrors: serverCalls.sendErrors.map(mapCall), - sendTransactions: serverCalls.sendTransactions.map(mapCall) - } - done(calls) - } catch (e) { - throw e - } - }) - .catch(function(reason) { - console.log('reason', reason) - try { - done({ error: reason.message || JSON.stringify(reason) }) - } catch (e) { - done({ - error: 'Failed serializing rejection reason: ' + e.message - }) - } - }) - } - } - - checkCalls() - apmServerMock.subscription.subscribe(checkCalls) - }) - - expect(serverCalls).toBeTruthy() - console.log(JSON.stringify(serverCalls, null, 2)) - if (serverCalls.error) { - fail(serverCalls.error) - } expect(serverCalls.sendErrors.length).toBe(1) var errorPayload = serverCalls.sendErrors[0].args[0][0] expect( diff --git a/packages/rum/test/e2e/react/app.e2e-spec.js b/packages/rum/test/e2e/react/app.e2e-spec.js deleted file mode 100644 index b3861f4c3..000000000 --- a/packages/rum/test/e2e/react/app.e2e-spec.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2017-present, Elasticsearch BV - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -const { allowSomeBrowserErrors } = require('../../../../../dev-utils/webdriver') - -describe('react app', function() { - it('should run the react app', function() { - browser.url('/test/e2e/react/') - browser.waitUntil( - () => { - return $('#test-element').getText() === 'Passed' - }, - 5000, - 'expected element #test-element' - ) - - const serverCalls = browser.executeAsync(function(done) { - var apmServerMock = window.elasticApm.serviceFactory.getService( - 'ApmServer' - ) - - function checkCalls() { - var serverCalls = apmServerMock.calls - var validCalls = - serverCalls.sendTransactions && - serverCalls.sendTransactions.length > 1 - - if (validCalls) { - Promise.all([ - serverCalls.sendTransactions[0].returnValue, - serverCalls.sendTransactions[1].returnValue - ]) - .then(function() { - function mapCall(c) { - return { args: c.args, mocked: c.mocked } - } - try { - var calls = { - sendTransactions: serverCalls.sendTransactions.map(mapCall) - } - done(calls) - } catch (e) { - throw e - } - }) - .catch(function(reason) { - console.log('reason', reason) - try { - done({ error: reason.message || JSON.stringify(reason) }) - } catch (e) { - done({ - error: 'Failed serializing rejection reason: ' + e.message - }) - } - }) - } - } - - checkCalls() - apmServerMock.subscription.subscribe(checkCalls) - }) - - expect(serverCalls).toBeTruthy() - console.log(JSON.stringify(serverCalls, null, 2)) - if (serverCalls.error) { - fail(serverCalls.error) - } - - expect(serverCalls.sendTransactions.length).toBe(2) - var pageLoadTransaction = serverCalls.sendTransactions[0].args[0][0] - expect(pageLoadTransaction.type).toBe('page-load') - expect(pageLoadTransaction.name).toBe('App Load') - expect(pageLoadTransaction.spans.length).toBeGreaterThan(1) - - var routeChangeTransaction = serverCalls.sendTransactions[1].args[0][0] - expect(routeChangeTransaction.type).toBe('route-change') - expect(routeChangeTransaction.name).toBe('Main - /') - expect(routeChangeTransaction.spans.length).toBeGreaterThan(1) - - var fetchDataSpan = routeChangeTransaction.spans.find(function(s) { - return s.name === 'GET /test/e2e/react/data.json' - }) - expect(fetchDataSpan).toBeDefined() - - return allowSomeBrowserErrors() - }) -}) diff --git a/packages/rum/test/specs/apm-base.spec.js b/packages/rum/test/specs/apm-base.spec.js index 6a41887b0..74d053c83 100644 --- a/packages/rum/test/specs/apm-base.spec.js +++ b/packages/rum/test/specs/apm-base.spec.js @@ -48,7 +48,7 @@ describe('ApmBase', function() { var tr = trService.getCurrentTransaction() expect(tr.name).toBe('Unknown') expect(tr.type).toBe('page-load') - spyOn(tr, 'detectFinish') + spyOn(tr, 'detectFinish').and.callThrough() window.addEventListener('load', function() { setTimeout(() => { expect(tr.detectFinish).toHaveBeenCalled() @@ -105,10 +105,12 @@ describe('ApmBase', function() { expect(configService.get('pageLoadTransactionName')).toBe('test') - var tr = apmBase.startTransaction('test-transaction', 'test-type') + var tr = apmBase.startTransaction('test-transaction', 'test-type', { + canReuse: true + }) expect(tr).toBeDefined() expect(tr.name).toBe('test-transaction') - expect(tr.type).toBe('test-type') + expect(tr.type).toBe('page-load') spyOn(tr, 'startSpan').and.callThrough() apmBase.startSpan('test-span', 'test-type')