dd-trace 4.1.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,8 @@ require,@datadog/native-iast-rewriter,Apache license 2.0,Copyright 2018 Datadog
5
5
  require,@datadog/native-iast-taint-tracking,Apache license 2.0,Copyright 2018 Datadog Inc.
6
6
  require,@datadog/pprof,Apache license 2.0,Copyright 2019 Google Inc.
7
7
  require,@datadog/sketches-js,Apache license 2.0,Copyright 2020 Datadog Inc.
8
+ require,@opentelemetry/api,Apache license 2.0,Copyright OpenTelemetry Authors
9
+ require,@opentelemetry/core,Apache license 2.0,Copyright OpenTelemetry Authors
8
10
  require,crypto-randomuuid,MIT,Copyright 2021 Node.js Foundation and contributors
9
11
  require,diagnostics_channel,MIT,Copyright 2021 Simon D.
10
12
  require,ignore,MIT,Copyright 2013 Kael Zhang and contributors
@@ -51,6 +53,7 @@ dev,glob,ISC,Copyright Isaac Z. Schlueter and Contributors
51
53
  dev,graphql,MIT,Copyright 2015 Facebook Inc.
52
54
  dev,int64-buffer,MIT,Copyright 2015-2016 Yusuke Kawasaki
53
55
  dev,jszip,MIT,Copyright 2015-2016 Stuart Knightley and contributors
56
+ dev,knex,MIT,Copyright (c) 2013-present Tim Griesser
54
57
  dev,mkdirp,MIT,Copyright 2010 James Halliday
55
58
  dev,mocha,MIT,Copyright 2011-2018 JS Foundation and contributors https://js.foundation
56
59
  dev,multer,MIT,Copyright 2014 Hage Yaapa
package/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import { ClientRequest, IncomingMessage, OutgoingMessage, ServerResponse } from
2
2
  import { LookupFunction } from 'net';
3
3
  import * as opentracing from "opentracing";
4
4
  import { SpanOptions } from "opentracing/lib/tracer";
5
+ import * as otel from "@opentelemetry/api";
5
6
 
6
7
  export { SpanOptions };
7
8
 
@@ -118,6 +119,8 @@ export declare interface Tracer extends opentracing.Tracer {
118
119
  setUser (user: User): Tracer;
119
120
 
120
121
  appsec: Appsec;
122
+
123
+ TracerProvider: opentelemetry.TracerProvider;
121
124
  }
122
125
 
123
126
  export declare interface TraceOptions extends Analyzable {
@@ -1579,6 +1582,277 @@ declare namespace plugins {
1579
1582
  interface winston extends Integration {}
1580
1583
  }
1581
1584
 
1585
+ export namespace opentelemetry {
1586
+ /**
1587
+ * A registry for creating named {@link Tracer}s.
1588
+ */
1589
+ export interface TracerProvider extends otel.TracerProvider {
1590
+ /**
1591
+ * Construct a new TracerProvider to register with @opentelemetry/api
1592
+ *
1593
+ * @returns TracerProvider A TracerProvider instance
1594
+ */
1595
+ new(): TracerProvider;
1596
+
1597
+ /**
1598
+ * Returns a Tracer, creating one if one with the given name and version is
1599
+ * not already created.
1600
+ *
1601
+ * This function may return different Tracer types (e.g.
1602
+ * {@link NoopTracerProvider} vs. a functional tracer).
1603
+ *
1604
+ * @param name The name of the tracer or instrumentation library.
1605
+ * @param version The version of the tracer or instrumentation library.
1606
+ * @param options The options of the tracer or instrumentation library.
1607
+ * @returns Tracer A Tracer with the given name and version
1608
+ */
1609
+ getTracer(name: string, version?: string): Tracer;
1610
+
1611
+ /**
1612
+ * Register this tracer provider with @opentelemetry/api
1613
+ */
1614
+ register(): void;
1615
+ }
1616
+
1617
+ /**
1618
+ * Tracer provides an interface for creating {@link Span}s.
1619
+ */
1620
+ export interface Tracer extends otel.Tracer {
1621
+ /**
1622
+ * Starts a new {@link Span}. Start the span without setting it on context.
1623
+ *
1624
+ * This method do NOT modify the current Context.
1625
+ *
1626
+ * @param name The name of the span
1627
+ * @param [options] SpanOptions used for span creation
1628
+ * @param [context] Context to use to extract parent
1629
+ * @returns Span The newly created span
1630
+ * @example
1631
+ * const span = tracer.startSpan('op');
1632
+ * span.setAttribute('key', 'value');
1633
+ * span.end();
1634
+ */
1635
+ startSpan(name: string, options?: SpanOptions, context?: Context): Span;
1636
+
1637
+ /**
1638
+ * Starts a new {@link Span} and calls the given function passing it the
1639
+ * created span as first argument.
1640
+ * Additionally the new span gets set in context and this context is activated
1641
+ * for the duration of the function call.
1642
+ *
1643
+ * @param name The name of the span
1644
+ * @param [options] SpanOptions used for span creation
1645
+ * @param [context] Context to use to extract parent
1646
+ * @param fn function called in the context of the span and receives the newly created span as an argument
1647
+ * @returns return value of fn
1648
+ * @example
1649
+ * const something = tracer.startActiveSpan('op', span => {
1650
+ * try {
1651
+ * do some work
1652
+ * span.setStatus({code: SpanStatusCode.OK});
1653
+ * return something;
1654
+ * } catch (err) {
1655
+ * span.setStatus({
1656
+ * code: SpanStatusCode.ERROR,
1657
+ * message: err.message,
1658
+ * });
1659
+ * throw err;
1660
+ * } finally {
1661
+ * span.end();
1662
+ * }
1663
+ * });
1664
+ *
1665
+ * @example
1666
+ * const span = tracer.startActiveSpan('op', span => {
1667
+ * try {
1668
+ * do some work
1669
+ * return span;
1670
+ * } catch (err) {
1671
+ * span.setStatus({
1672
+ * code: SpanStatusCode.ERROR,
1673
+ * message: err.message,
1674
+ * });
1675
+ * throw err;
1676
+ * }
1677
+ * });
1678
+ * do some more work
1679
+ * span.end();
1680
+ */
1681
+ startActiveSpan<F extends (span: Span) => unknown>(name: string, fn: F): ReturnType<F>;
1682
+ startActiveSpan<F extends (span: Span) => unknown>(name: string, options: SpanOptions, fn: F): ReturnType<F>;
1683
+ startActiveSpan<F extends (span: Span) => unknown>(name: string, options: SpanOptions, context: otel.Context, fn: F): ReturnType<F>;
1684
+ }
1685
+
1686
+ /**
1687
+ * An interface that represents a span. A span represents a single operation
1688
+ * within a trace. Examples of span might include remote procedure calls or a
1689
+ * in-process function calls to sub-components. A Trace has a single, top-level
1690
+ * "root" Span that in turn may have zero or more child Spans, which in turn
1691
+ * may have children.
1692
+ *
1693
+ * Spans are created by the {@link Tracer.startSpan} method.
1694
+ */
1695
+ export interface Span extends otel.Span {
1696
+ /**
1697
+ * Returns the {@link SpanContext} object associated with this Span.
1698
+ *
1699
+ * Get an immutable, serializable identifier for this span that can be used
1700
+ * to create new child spans. Returned SpanContext is usable even after the
1701
+ * span ends.
1702
+ *
1703
+ * @returns the SpanContext object associated with this Span.
1704
+ */
1705
+ spanContext(): SpanContext;
1706
+
1707
+ /**
1708
+ * Sets an attribute to the span.
1709
+ *
1710
+ * Sets a single Attribute with the key and value passed as arguments.
1711
+ *
1712
+ * @param key the key for this attribute.
1713
+ * @param value the value for this attribute. Setting a value null or
1714
+ * undefined is invalid and will result in undefined behavior.
1715
+ */
1716
+ setAttribute(key: string, value: SpanAttributeValue): this;
1717
+
1718
+ /**
1719
+ * Sets attributes to the span.
1720
+ *
1721
+ * @param attributes the attributes that will be added.
1722
+ * null or undefined attribute values
1723
+ * are invalid and will result in undefined behavior.
1724
+ */
1725
+ setAttributes(attributes: SpanAttributes): this;
1726
+
1727
+ /**
1728
+ * Adds an event to the Span.
1729
+ *
1730
+ * @param name the name of the event.
1731
+ * @param [attributesOrStartTime] the attributes that will be added; these are
1732
+ * associated with this event. Can be also a start time
1733
+ * if type is {@type TimeInput} and 3rd param is undefined
1734
+ * @param [startTime] start time of the event.
1735
+ */
1736
+ addEvent(name: string, attributesOrStartTime?: SpanAttributes | TimeInput, startTime?: TimeInput): this;
1737
+
1738
+ /**
1739
+ * Sets a status to the span. If used, this will override the default Span
1740
+ * status. Default is {@link SpanStatusCode.UNSET}. SetStatus overrides the value
1741
+ * of previous calls to SetStatus on the Span.
1742
+ *
1743
+ * @param status the SpanStatus to set.
1744
+ */
1745
+ setStatus(status: SpanStatus): this;
1746
+
1747
+ /**
1748
+ * Updates the Span name.
1749
+ *
1750
+ * This will override the name provided via {@link Tracer.startSpan}.
1751
+ *
1752
+ * Upon this update, any sampling behavior based on Span name will depend on
1753
+ * the implementation.
1754
+ *
1755
+ * @param name the Span name.
1756
+ */
1757
+ updateName(name: string): this;
1758
+
1759
+ /**
1760
+ * Marks the end of Span execution.
1761
+ *
1762
+ * Call to End of a Span MUST not have any effects on child spans. Those may
1763
+ * still be running and can be ended later.
1764
+ *
1765
+ * Do not return `this`. The Span generally should not be used after it
1766
+ * is ended so chaining is not desired in this context.
1767
+ *
1768
+ * @param [endTime] the time to set as Span's end time. If not provided,
1769
+ * use the current time as the span's end time.
1770
+ */
1771
+ end(endTime?: TimeInput): void;
1772
+
1773
+ /**
1774
+ * Returns the flag whether this span will be recorded.
1775
+ *
1776
+ * @returns true if this Span is active and recording information like events
1777
+ * with the `AddEvent` operation and attributes using `setAttributes`.
1778
+ */
1779
+ isRecording(): boolean;
1780
+
1781
+ /**
1782
+ * Sets exception as a span event
1783
+ * @param exception the exception the only accepted values are string or Error
1784
+ * @param [time] the time to set as Span's event time. If not provided,
1785
+ * use the current time.
1786
+ */
1787
+ recordException(exception: Exception, time?: TimeInput): void;
1788
+ }
1789
+
1790
+ /**
1791
+ * A SpanContext represents the portion of a {@link Span} which must be
1792
+ * serialized and propagated along side of a {@link Baggage}.
1793
+ */
1794
+ export interface SpanContext extends otel.SpanContext {
1795
+ /**
1796
+ * The ID of the trace that this span belongs to. It is worldwide unique
1797
+ * with practically sufficient probability by being made as 16 randomly
1798
+ * generated bytes, encoded as a 32 lowercase hex characters corresponding to
1799
+ * 128 bits.
1800
+ */
1801
+ traceId: string;
1802
+
1803
+ /**
1804
+ * The ID of the Span. It is globally unique with practically sufficient
1805
+ * probability by being made as 8 randomly generated bytes, encoded as a 16
1806
+ * lowercase hex characters corresponding to 64 bits.
1807
+ */
1808
+ spanId: string;
1809
+
1810
+ /**
1811
+ * Only true if the SpanContext was propagated from a remote parent.
1812
+ */
1813
+ isRemote?: boolean;
1814
+
1815
+ /**
1816
+ * Trace flags to propagate.
1817
+ *
1818
+ * It is represented as 1 byte (bitmap). Bit to represent whether trace is
1819
+ * sampled or not. When set, the least significant bit documents that the
1820
+ * caller may have recorded trace data. A caller who does not record trace
1821
+ * data out-of-band leaves this flag unset.
1822
+ *
1823
+ * see {@link TraceFlags} for valid flag values.
1824
+ */
1825
+ traceFlags: number;
1826
+
1827
+ /**
1828
+ * Tracing-system-specific info to propagate.
1829
+ *
1830
+ * The tracestate field value is a `list` as defined below. The `list` is a
1831
+ * series of `list-members` separated by commas `,`, and a list-member is a
1832
+ * key/value pair separated by an equals sign `=`. Spaces and horizontal tabs
1833
+ * surrounding `list-members` are ignored. There can be a maximum of 32
1834
+ * `list-members` in a `list`.
1835
+ * More Info: https://www.w3.org/TR/trace-context/#tracestate-field
1836
+ *
1837
+ * Examples:
1838
+ * Single tracing system (generic format):
1839
+ * tracestate: rojo=00f067aa0ba902b7
1840
+ * Multiple tracing systems (with different formatting):
1841
+ * tracestate: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE
1842
+ */
1843
+ traceState?: TraceState;
1844
+ }
1845
+
1846
+ export type Context = otel.Context;
1847
+ export type Exception = otel.Exception;
1848
+ export type SpanAttributes = otel.SpanAttributes;
1849
+ export type SpanAttributeValue = otel.SpanAttributeValue;
1850
+ export type SpanOptions = otel.SpanOptions;
1851
+ export type SpanStatus = otel.SpanStatus;
1852
+ export type TimeInput = otel.TimeInput;
1853
+ export type TraceState = otel.TraceState;
1854
+ }
1855
+
1582
1856
  /**
1583
1857
  * Singleton returned by the module. It has to be initialized before it will
1584
1858
  * start tracing. If not initialized, or initialized and disabled, it will use
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -19,7 +19,7 @@
19
19
  "test:appsec:ci": "nyc --no-clean --include \"packages/dd-trace/src/appsec/**/*.js\" --exclude \"packages/dd-trace/test/appsec/**/*.plugin.spec.js\" -- npm run test:appsec",
20
20
  "test:appsec:plugins": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/appsec/**/*.@($(echo $PLUGINS)).plugin.spec.js\"",
21
21
  "test:appsec:plugins:ci": "yarn services && nyc --no-clean --include \"packages/dd-trace/test/appsec/**/*.@($(echo $PLUGINS)).plugin.spec.js\" -- npm run test:appsec:plugins",
22
- "test:trace:core": "tap packages/dd-trace/test/*.spec.js \"packages/dd-trace/test/{ci-visibility,encode,exporters,opentracing,plugins,telemetry}/**/*.spec.js\"",
22
+ "test:trace:core": "tap packages/dd-trace/test/*.spec.js \"packages/dd-trace/test/{ci-visibility,encode,exporters,opentelemetry,opentracing,plugins,telemetry}/**/*.spec.js\"",
23
23
  "test:trace:core:ci": "npm run test:trace:core -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/**/*.js\"",
24
24
  "test:instrumentations": "mocha --colors -r 'packages/dd-trace/test/setup/mocha.js' 'packages/datadog-instrumentations/test/**/*.spec.js'",
25
25
  "test:instrumentations:ci": "nyc --no-clean --include 'packages/datadog-instrumentations/src/**/*.js' -- npm run test:instrumentations",
@@ -72,6 +72,8 @@
72
72
  "@datadog/native-metrics": "^2.0.0",
73
73
  "@datadog/pprof": "^2.2.1",
74
74
  "@datadog/sketches-js": "^2.1.0",
75
+ "@opentelemetry/api": "^1.0.0",
76
+ "@opentelemetry/core": "<1.4.0",
75
77
  "crypto-randomuuid": "^1.0.0",
76
78
  "diagnostics_channel": "^1.1.0",
77
79
  "ignore": "^5.2.0",
@@ -120,6 +122,7 @@
120
122
  "graphql": "0.13.2",
121
123
  "int64-buffer": "^0.1.9",
122
124
  "jszip": "^3.5.0",
125
+ "knex": "^2.4.2",
123
126
  "mkdirp": "^0.5.1",
124
127
  "mocha": "8",
125
128
  "msgpack-lite": "^0.1.26",
@@ -39,7 +39,19 @@ module.exports.setup = function (build) {
39
39
 
40
40
  if (args.namespace === 'file' && packagesOfInterest.has(packageName)) {
41
41
  // The file namespace is used when requiring files from disk in userland
42
- const pathToPackageJson = require.resolve(`${packageName}/package.json`, { paths: [ args.resolveDir ] })
42
+
43
+ let pathToPackageJson
44
+ try {
45
+ pathToPackageJson = require.resolve(`${packageName}/package.json`, { paths: [ args.resolveDir ] })
46
+ } catch (err) {
47
+ if (err.code === 'MODULE_NOT_FOUND') {
48
+ console.warn(`Unable to open "${packageName}/package.json". Is the "${packageName}" package dead code?`)
49
+ return
50
+ } else {
51
+ throw err
52
+ }
53
+ }
54
+
43
55
  const pkg = require(pathToPackageJson)
44
56
 
45
57
  if (DEBUG) {
@@ -31,16 +31,19 @@ function wrapQuery (query) {
31
31
  const asyncResource = new AsyncResource('bound-anonymous-fn')
32
32
  const processId = this.processID
33
33
 
34
- let pgQuery = arguments[0] && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] }
34
+ const pgQuery = arguments[0] && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] }
35
+
36
+ // shallow clone the existing query to swap out .text field
37
+ let newQuery = { ...pgQuery }
35
38
 
36
39
  return asyncResource.runInAsyncScope(() => {
37
40
  startCh.publish({
38
41
  params: this.connectionParameters,
39
- query: pgQuery,
42
+ query: newQuery,
40
43
  processId
41
44
  })
42
45
 
43
- arguments[0] = pgQuery
46
+ arguments[0] = newQuery
44
47
 
45
48
  const finish = asyncResource.bind(function (error) {
46
49
  if (error) {
@@ -53,24 +56,24 @@ function wrapQuery (query) {
53
56
  const queryQueue = this.queryQueue || this._queryQueue
54
57
  const activeQuery = this.activeQuery || this._activeQuery
55
58
 
56
- pgQuery = queryQueue[queryQueue.length - 1] || activeQuery
59
+ newQuery = queryQueue[queryQueue.length - 1] || activeQuery
57
60
 
58
- if (!pgQuery) {
61
+ if (!newQuery) {
59
62
  return retval
60
63
  }
61
64
 
62
- if (pgQuery.callback) {
63
- const originalCallback = callbackResource.bind(pgQuery.callback)
64
- pgQuery.callback = function (err, res) {
65
+ if (newQuery.callback) {
66
+ const originalCallback = callbackResource.bind(newQuery.callback)
67
+ newQuery.callback = function (err, res) {
65
68
  finish(err)
66
69
  return originalCallback.apply(this, arguments)
67
70
  }
68
- } else if (pgQuery.once) {
69
- pgQuery
71
+ } else if (newQuery.once) {
72
+ newQuery
70
73
  .once('error', finish)
71
74
  .once('end', () => finish())
72
75
  } else {
73
- pgQuery.then(() => finish(), finish)
76
+ newQuery.then(() => finish(), finish)
74
77
  }
75
78
 
76
79
  try {
@@ -133,6 +133,8 @@ module.exports = (on, config) => {
133
133
  'git.branch': branch
134
134
  } = testEnvironmentMetadata
135
135
 
136
+ const finishedTestsByFile = {}
137
+
136
138
  const testConfiguration = {
137
139
  repositoryUrl,
138
140
  sha,
@@ -158,6 +160,44 @@ module.exports = (on, config) => {
158
160
  let isCodeCoverageEnabled = false
159
161
  let testsToSkip = []
160
162
 
163
+ function getTestSpan (testName, testSuite) {
164
+ const testSuiteTags = {
165
+ [TEST_COMMAND]: command,
166
+ [TEST_COMMAND]: command,
167
+ [TEST_MODULE]: TEST_FRAMEWORK_NAME
168
+ }
169
+ if (testSuiteSpan) {
170
+ testSuiteTags[TEST_SUITE_ID] = testSuiteSpan.context().toSpanId()
171
+ }
172
+ if (testSessionSpan && testModuleSpan) {
173
+ testSuiteTags[TEST_SESSION_ID] = testSessionSpan.context().toTraceId()
174
+ testSuiteTags[TEST_MODULE_ID] = testModuleSpan.context().toSpanId()
175
+ }
176
+
177
+ const {
178
+ childOf,
179
+ resource,
180
+ ...testSpanMetadata
181
+ } = getTestSpanMetadata(tracer, testName, testSuite, config)
182
+
183
+ const codeOwners = getCodeOwnersForFilename(testSuite, codeOwnersEntries)
184
+
185
+ if (codeOwners) {
186
+ testSpanMetadata[TEST_CODE_OWNERS] = codeOwners
187
+ }
188
+
189
+ return tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test`, {
190
+ childOf,
191
+ tags: {
192
+ [COMPONENT]: TEST_FRAMEWORK_NAME,
193
+ [ORIGIN_KEY]: CI_APP_ORIGIN,
194
+ ...testSpanMetadata,
195
+ ...testEnvironmentMetadata,
196
+ ...testSuiteTags
197
+ }
198
+ })
199
+ }
200
+
161
201
  on('before:run', (details) => {
162
202
  return getItrConfig(tracer, testConfiguration).then(({ err, itrConfig }) => {
163
203
  if (err) {
@@ -203,6 +243,58 @@ module.exports = (on, config) => {
203
243
  })
204
244
  })
205
245
  })
246
+ on('after:spec', (spec, { tests, stats }) => {
247
+ const cypressTests = tests || []
248
+ const finishedTests = finishedTestsByFile[spec.relative] || []
249
+
250
+ // Get tests that didn't go through `dd:afterEach` and haven't been skipped by ITR
251
+ // and create a skipped test span for each of them
252
+ cypressTests.filter(({ title }) => {
253
+ const cypressTestName = title.join(' ')
254
+ const isSkippedByItr = testsToSkip.find(test =>
255
+ cypressTestName === test.name && spec.relative === test.suite
256
+ )
257
+ const isTestFinished = finishedTests.find(({ testName }) => cypressTestName === testName)
258
+
259
+ return !isSkippedByItr && !isTestFinished
260
+ }).forEach(({ title }) => {
261
+ const skippedTestSpan = getTestSpan(title.join(' '), spec.relative)
262
+ skippedTestSpan.setTag(TEST_STATUS, 'skip')
263
+ skippedTestSpan.finish()
264
+ })
265
+
266
+ // Make sure that reported test statuses are the same as Cypress reports.
267
+ // This is not always the case, such as when an `after` hook fails:
268
+ // Cypress will report the last run test as failed, but we don't know that yet at `dd:afterEach`
269
+ let latestError
270
+ finishedTests.forEach((finishedTest) => {
271
+ const cypressTest = cypressTests.find(test => test.title.join(' ') === finishedTest.testName)
272
+ if (!cypressTest) {
273
+ return
274
+ }
275
+ if (cypressTest.displayError) {
276
+ latestError = new Error(cypressTest.displayError)
277
+ }
278
+ const cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.state]
279
+ // update test status
280
+ if (cypressTestStatus !== finishedTest.testStatus) {
281
+ finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus)
282
+ finishedTest.testSpan.setTag('error', latestError)
283
+ }
284
+ finishedTest.testSpan.finish(finishedTest.finishTime)
285
+ })
286
+
287
+ if (testSuiteSpan) {
288
+ const status = getSuiteStatus(stats)
289
+ testSuiteSpan.setTag(TEST_STATUS, status)
290
+
291
+ if (latestError) {
292
+ testSuiteSpan.setTag('error', latestError)
293
+ }
294
+ testSuiteSpan.finish()
295
+ testSuiteSpan = null
296
+ }
297
+ })
206
298
 
207
299
  on('after:run', (suiteStats) => {
208
300
  if (testSessionSpan && testModuleSpan) {
@@ -254,15 +346,6 @@ module.exports = (on, config) => {
254
346
  })
255
347
  return null
256
348
  },
257
- 'dd:testSuiteFinish': (stats) => {
258
- if (testSuiteSpan) {
259
- const status = getSuiteStatus(stats)
260
- testSuiteSpan.setTag(TEST_STATUS, status)
261
- testSuiteSpan.finish()
262
- testSuiteSpan = null
263
- }
264
- return null
265
- },
266
349
  'dd:beforeEach': (test) => {
267
350
  const { testName, testSuite } = test
268
351
  // skip test
@@ -272,47 +355,14 @@ module.exports = (on, config) => {
272
355
  return { shouldSkip: true }
273
356
  }
274
357
 
275
- const testSuiteTags = {
276
- [TEST_COMMAND]: command,
277
- [TEST_COMMAND]: command,
278
- [TEST_MODULE]: TEST_FRAMEWORK_NAME
279
- }
280
- if (testSuiteSpan) {
281
- testSuiteTags[TEST_SUITE_ID] = testSuiteSpan.context().toSpanId()
282
- }
283
- if (testSessionSpan && testModuleSpan) {
284
- testSuiteTags[TEST_SESSION_ID] = testSessionSpan.context().toTraceId()
285
- testSuiteTags[TEST_MODULE_ID] = testModuleSpan.context().toSpanId()
286
- }
287
-
288
- const {
289
- childOf,
290
- resource,
291
- ...testSpanMetadata
292
- } = getTestSpanMetadata(tracer, testName, testSuite, config)
293
-
294
- const codeOwners = getCodeOwnersForFilename(testSuite, codeOwnersEntries)
295
-
296
- if (codeOwners) {
297
- testSpanMetadata[TEST_CODE_OWNERS] = codeOwners
298
- }
299
-
300
358
  if (!activeSpan) {
301
- activeSpan = tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test`, {
302
- childOf,
303
- tags: {
304
- [COMPONENT]: TEST_FRAMEWORK_NAME,
305
- [ORIGIN_KEY]: CI_APP_ORIGIN,
306
- ...testSpanMetadata,
307
- ...testEnvironmentMetadata,
308
- ...testSuiteTags
309
- }
310
- })
359
+ activeSpan = getTestSpan(testName, testSuite)
311
360
  }
361
+
312
362
  return activeSpan ? { traceId: activeSpan.context().toTraceId() } : {}
313
363
  },
314
364
  'dd:afterEach': ({ test, coverage }) => {
315
- const { state, error, isRUMActive, testSourceLine } = test
365
+ const { state, error, isRUMActive, testSourceLine, testSuite, testName } = test
316
366
  if (activeSpan) {
317
367
  if (coverage && tracer._tracer._exporter.exportCoverage && isCodeCoverageEnabled) {
318
368
  const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
@@ -326,8 +376,9 @@ module.exports = (on, config) => {
326
376
  }
327
377
  tracer._tracer._exporter.exportCoverage(formattedCoverage)
328
378
  }
379
+ const testStatus = CYPRESS_STATUS_TO_TEST_STATUS[state]
380
+ activeSpan.setTag(TEST_STATUS, testStatus)
329
381
 
330
- activeSpan.setTag(TEST_STATUS, CYPRESS_STATUS_TO_TEST_STATUS[state])
331
382
  if (error) {
332
383
  activeSpan.setTag('error', error)
333
384
  }
@@ -337,7 +388,18 @@ module.exports = (on, config) => {
337
388
  if (testSourceLine) {
338
389
  activeSpan.setTag(TEST_SOURCE_START, testSourceLine)
339
390
  }
340
- activeSpan.finish()
391
+ const finishedTest = {
392
+ testName,
393
+ testStatus,
394
+ finishTime: activeSpan._getTime(), // we store the finish time here
395
+ testSpan: activeSpan
396
+ }
397
+ if (finishedTestsByFile[testSuite]) {
398
+ finishedTestsByFile[testSuite].push(finishedTest)
399
+ } else {
400
+ finishedTestsByFile[testSuite] = [finishedTest]
401
+ }
402
+ // test spans are finished at after:spec
341
403
  }
342
404
  activeSpan = null
343
405
  return null
@@ -16,9 +16,10 @@ before(() => {
16
16
  })
17
17
 
18
18
  after(() => {
19
- cy.task('dd:testSuiteFinish', Cypress.mocha.getRunner().stats)
20
19
  cy.window().then(win => {
21
- win.dispatchEvent(new Event('beforeunload'))
20
+ if (win.DD_RUM) {
21
+ win.dispatchEvent(new Event('beforeunload'))
22
+ }
22
23
  })
23
24
  })
24
25
 
@@ -130,7 +130,7 @@ class RouterPlugin extends WebPlugin {
130
130
  route = context.stack.join('')
131
131
 
132
132
  // Longer route is more likely to be the actual route handler route.
133
- if (route.length > context.route.length) {
133
+ if (isMoreSpecificThan(route, context.route)) {
134
134
  context.route = route
135
135
  }
136
136
  } else {
@@ -148,4 +148,15 @@ class RouterPlugin extends WebPlugin {
148
148
  }
149
149
  }
150
150
 
151
+ function isMoreSpecificThan (routeA, routeB) {
152
+ if (!routeIsRegex(routeA) && routeIsRegex(routeB)) {
153
+ return true
154
+ }
155
+ return routeA.length > routeB.length
156
+ }
157
+
158
+ function routeIsRegex (route) {
159
+ return route.includes('(/') && route.includes('/)')
160
+ }
161
+
151
162
  module.exports = RouterPlugin