dd-trace 5.46.0 → 5.47.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.
Files changed (41) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/package.json +8 -9
  3. package/packages/datadog-instrumentations/orchestrion.yml +52 -0
  4. package/packages/datadog-instrumentations/src/cucumber.js +2 -1
  5. package/packages/datadog-instrumentations/src/jest.js +11 -2
  6. package/packages/datadog-instrumentations/src/langchain.js +49 -53
  7. package/packages/datadog-instrumentations/src/mocha/main.js +1 -1
  8. package/packages/datadog-instrumentations/src/mocha/utils.js +11 -3
  9. package/packages/datadog-instrumentations/src/orchestrion-config/index.js +5 -0
  10. package/packages/datadog-instrumentations/src/playwright.js +14 -2
  11. package/packages/datadog-instrumentations/src/vitest.js +11 -3
  12. package/packages/datadog-plugin-cucumber/src/index.js +11 -4
  13. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +17 -5
  14. package/packages/datadog-plugin-jest/src/index.js +11 -4
  15. package/packages/datadog-plugin-langchain/src/index.js +18 -12
  16. package/packages/datadog-plugin-langchain/src/tracing.js +66 -6
  17. package/packages/datadog-plugin-mocha/src/index.js +17 -5
  18. package/packages/datadog-plugin-mongodb-core/src/index.js +5 -1
  19. package/packages/datadog-plugin-playwright/src/index.js +10 -3
  20. package/packages/datadog-plugin-vitest/src/index.js +13 -8
  21. package/packages/datadog-shimmer/src/shimmer.js +3 -42
  22. package/packages/dd-trace/src/appsec/iast/taint-tracking/filter.js +3 -3
  23. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +0 -3
  24. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +24 -11
  25. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +3 -32
  26. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +98 -56
  27. package/packages/dd-trace/src/config.js +9 -0
  28. package/packages/dd-trace/src/exporters/common/docker.js +37 -7
  29. package/packages/dd-trace/src/exporters/common/request.js +1 -4
  30. package/packages/dd-trace/src/llmobs/plugins/base.js +2 -2
  31. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +62 -3
  32. package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -0
  33. package/packages/dd-trace/src/llmobs/plugins/vertexai.js +2 -1
  34. package/packages/dd-trace/src/log/index.js +2 -0
  35. package/packages/dd-trace/src/log/writer.js +19 -2
  36. package/packages/dd-trace/src/opentracing/propagation/text_map.js +17 -3
  37. package/packages/dd-trace/src/opentracing/span.js +10 -0
  38. package/packages/dd-trace/src/plugins/util/test.js +7 -0
  39. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -5
  40. package/packages/dd-trace/src/profiling/profilers/wall.js +3 -1
  41. package/packages/dd-trace/src/proxy.js +5 -1
@@ -2,10 +2,10 @@ Component,Origin,License,Copyright
2
2
  require,@datadog/libdatadog,Apache license 2.0,Copyright 2024 Datadog Inc.
3
3
  require,@datadog/native-appsec,Apache license 2.0,Copyright 2018 Datadog Inc.
4
4
  require,@datadog/native-metrics,Apache license 2.0,Copyright 2018 Datadog Inc.
5
- require,@datadog/wasm-js-rewriter,Apache license 2.0,Copyright 2018 Datadog Inc.
6
5
  require,@datadog/native-iast-taint-tracking,Apache license 2.0,Copyright 2018 Datadog Inc.
7
6
  require,@datadog/pprof,Apache license 2.0,Copyright 2019 Google Inc.
8
7
  require,@datadog/sketches-js,Apache license 2.0,Copyright 2020 Datadog Inc.
8
+ require,@datadog/wasm-js-rewriter,Apache license 2.0,Copyright 2018 Datadog Inc.
9
9
  require,@opentelemetry/api,Apache license 2.0,Copyright OpenTelemetry Authors
10
10
  require,@opentelemetry/core,Apache license 2.0,Copyright OpenTelemetry Authors
11
11
  require,@isaacs/ttlcache,ISC,Copyright (c) 2022-2023 - Isaac Z. Schlueter and Contributors
@@ -38,7 +38,6 @@ dev,@eslint/eslintrc,MIT,Copyright OpenJS Foundation and other contributors, <ww
38
38
  dev,@eslint/js,MIT,Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
39
39
  dev,@msgpack/msgpack,ISC,Copyright 2019 The MessagePack Community
40
40
  dev,@stylistic/eslint-plugin-js,MIT,Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
41
- dev,application-config-path,MIT,Copyright (c) 2015, 2023 Linus Unnebäck
42
41
  dev,autocannon,MIT,Copyright 2016 Matteo Collina
43
42
  dev,aws-sdk,Apache 2.0,Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
44
43
  dev,axios,MIT,Copyright 2014-present Matt Zabriskie
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.46.0",
3
+ "version": "5.47.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -84,18 +84,18 @@
84
84
  "node": ">=18"
85
85
  },
86
86
  "dependencies": {
87
- "@datadog/libdatadog": "^0.5.0",
88
- "@datadog/native-appsec": "8.5.1",
89
- "@datadog/wasm-js-rewriter": "3.1.0",
90
- "@datadog/native-iast-taint-tracking": "3.3.0",
91
- "@datadog/native-metrics": "^3.1.0",
92
- "@datadog/pprof": "5.7.0",
87
+ "@datadog/libdatadog": "^0.5.1",
88
+ "@datadog/native-appsec": "8.5.2",
89
+ "@datadog/native-iast-taint-tracking": "3.3.1",
90
+ "@datadog/native-metrics": "^3.1.1",
91
+ "@datadog/pprof": "5.7.1",
93
92
  "@datadog/sketches-js": "^2.1.0",
93
+ "@datadog/wasm-js-rewriter": "4.0.0",
94
94
  "@isaacs/ttlcache": "^1.4.1",
95
95
  "@opentelemetry/api": ">=1.0.0 <1.9.0",
96
96
  "@opentelemetry/core": "^1.14.0",
97
97
  "crypto-randomuuid": "^1.0.0",
98
- "dc-polyfill": "0.1.6",
98
+ "dc-polyfill": "0.1.8",
99
99
  "ignore": "^5.2.4",
100
100
  "import-in-the-middle": "1.13.1",
101
101
  "istanbul-lib-coverage": "3.2.0",
@@ -125,7 +125,6 @@
125
125
  "@msgpack/msgpack": "^3.0.0-beta3",
126
126
  "@stylistic/eslint-plugin-js": "^3.0.1",
127
127
  "@types/node": "^16.0.0",
128
- "application-config-path": "^0.1.1",
129
128
  "autocannon": "^4.5.2",
130
129
  "aws-sdk": "^2.1446.0",
131
130
  "axios": "^1.8.2",
@@ -0,0 +1,52 @@
1
+ version: 1
2
+ dc_module: dc-polyfill
3
+ instrumentations:
4
+ - module_name: "@langchain/core"
5
+ version_range: ">=0.1.0"
6
+ file_path: dist/runnables/base.js
7
+ function_query:
8
+ name: invoke
9
+ type: method
10
+ kind: async
11
+ class: RunnableSequence
12
+ operator: tracePromise
13
+ channel_name: "RunnableSequence_invoke"
14
+ - module_name: "@langchain/core"
15
+ version_range: ">=0.1.0"
16
+ file_path: dist/runnables/base.js
17
+ function_query:
18
+ name: batch
19
+ type: method
20
+ kind: async
21
+ class: RunnableSequence
22
+ operator: tracePromise
23
+ channel_name: "RunnableSequence_batch"
24
+ - module_name: "@langchain/core"
25
+ version_range: ">=0.1.0"
26
+ file_path: dist/language_models/chat_models.js
27
+ function_query:
28
+ name: generate
29
+ type: method
30
+ kind: async
31
+ class: BaseChatModel
32
+ operator: tracePromise
33
+ channel_name: "BaseChatModel_generate"
34
+ - module_name: "@langchain/core"
35
+ version_range: ">=0.1.0"
36
+ file_path: dist/language_models/llms.js
37
+ function_query:
38
+ name: generate
39
+ type: method
40
+ kind: async
41
+ operator: tracePromise
42
+ channel_name: "BaseLLM_generate"
43
+ - module_name: "@langchain/core"
44
+ version_range: ">=0.1.0"
45
+ file_path: dist/embeddings.js
46
+ function_query:
47
+ name: constructor
48
+ type: method
49
+ kind: sync
50
+ class: Embeddings
51
+ operator: traceSync
52
+ channel_name: "Embeddings_constructor"
@@ -267,6 +267,7 @@ function wrapRun (pl, isLatestVersion) {
267
267
 
268
268
  const failedAttemptAsyncResource = numAttemptToAsyncResource.get(numAttempt)
269
269
  const isFirstAttempt = numAttempt++ === 0
270
+ const isAtrRetry = !isFirstAttempt && isFlakyTestRetriesEnabled
270
271
 
271
272
  if (promises.hitBreakpointPromise) {
272
273
  await promises.hitBreakpointPromise
@@ -274,7 +275,7 @@ function wrapRun (pl, isLatestVersion) {
274
275
 
275
276
  failedAttemptAsyncResource.runInAsyncScope(() => {
276
277
  // the current span will be finished and a new one will be created
277
- testRetryCh.publish({ isFirstAttempt, error })
278
+ testRetryCh.publish({ isFirstAttempt, error, isAtrRetry })
278
279
  })
279
280
 
280
281
  const newAsyncResource = new AsyncResource('bound-anonymous-fn')
@@ -399,10 +399,11 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
399
399
 
400
400
  let attemptToFixPassed = false
401
401
  let failedAllTests = false
402
+ let isAttemptToFix = false
402
403
  if (this.isTestManagementTestsEnabled) {
403
404
  const testName = getJestTestName(event.test)
404
405
  const originalTestName = removeAttemptToFixStringFromTestName(testName)
405
- const isAttemptToFix = this.testManagementTestsForThisSuite?.attemptToFix?.includes(originalTestName)
406
+ isAttemptToFix = this.testManagementTestsForThisSuite?.attemptToFix?.includes(originalTestName)
406
407
  if (isAttemptToFix) {
407
408
  if (attemptToFixRetriedTestsStatuses.has(originalTestName)) {
408
409
  attemptToFixRetriedTestsStatuses.get(originalTestName).push(status)
@@ -423,6 +424,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
423
424
  }
424
425
  }
425
426
 
427
+ let isEfdRetry = false
426
428
  // We'll store the test statuses of the retries
427
429
  if (this.isKnownTestsEnabled) {
428
430
  const testName = getJestTestName(event.test)
@@ -431,6 +433,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
431
433
  if (isNewTest) {
432
434
  if (newTestsTestStatuses.has(originalTestName)) {
433
435
  newTestsTestStatuses.get(originalTestName).push(status)
436
+ isEfdRetry = true
434
437
  } else {
435
438
  newTestsTestStatuses.set(originalTestName, [status])
436
439
  }
@@ -466,12 +469,18 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
466
469
  })
467
470
  }
468
471
 
472
+ let isAtrRetry = false
473
+ if (this.isFlakyTestRetriesEnabled && event.test?.invocations > 1 && !isAttemptToFix && !isEfdRetry) {
474
+ isAtrRetry = true
475
+ }
476
+
469
477
  asyncResource.runInAsyncScope(() => {
470
478
  testFinishCh.publish({
471
479
  status,
472
480
  testStartLine: getTestLineStart(event.test.asyncError, this.testSuite),
473
481
  attemptToFixPassed,
474
- failedAllTests
482
+ failedAllTests,
483
+ isAtrRetry
475
484
  })
476
485
  })
477
486
 
@@ -1,31 +1,24 @@
1
1
  'use strict'
2
-
3
2
  const { addHook } = require('./helpers/instrument')
3
+
4
4
  const shimmer = require('../../datadog-shimmer')
5
5
 
6
6
  const tracingChannel = require('dc-polyfill').tracingChannel
7
7
 
8
- const invokeTracingChannel = tracingChannel('apm:langchain:invoke')
9
-
10
- function wrapLangChainPromise (fn, type, namespace = []) {
11
- return function () {
12
- if (!invokeTracingChannel.start.hasSubscribers) {
13
- return fn.apply(this, arguments)
14
- }
15
-
16
- // Runnable interfaces have an `lc_namespace` property
17
- const ns = this.lc_namespace || namespace
18
- const resource = [...ns, this.constructor.name].join('.')
19
-
20
- const ctx = {
21
- args: arguments,
22
- instance: this,
23
- type,
24
- resource
8
+ function wrap (obj, name, channelName, namespace) {
9
+ const channel = tracingChannel(channelName)
10
+ shimmer.wrap(obj, name, function (original) {
11
+ return function () {
12
+ if (!channel.start.hasSubscribers) {
13
+ return original.apply(this, arguments)
14
+ }
15
+ const ctx = { self: this, arguments }
16
+ if (namespace) {
17
+ ctx.namespace = namespace
18
+ }
19
+ return channel.tracePromise(original, ctx, this, ...arguments)
25
20
  }
26
-
27
- return invokeTracingChannel.tracePromise(fn, ctx, this, ...arguments)
28
- }
21
+ })
29
22
  }
30
23
 
31
24
  // langchain compiles into ESM and CommonJS, with ESM being the default and landing in the `.js` files
@@ -35,9 +28,10 @@ const extensions = ['js', 'cjs']
35
28
 
36
29
  for (const extension of extensions) {
37
30
  addHook({ name: '@langchain/core', file: `dist/runnables/base.${extension}`, versions: ['>=0.1'] }, exports => {
38
- const RunnableSequence = exports.RunnableSequence
39
- shimmer.wrap(RunnableSequence.prototype, 'invoke', invoke => wrapLangChainPromise(invoke, 'chain'))
40
- shimmer.wrap(RunnableSequence.prototype, 'batch', batch => wrapLangChainPromise(batch, 'chain'))
31
+ if (extension === 'cjs') {
32
+ wrap(exports.RunnableSequence.prototype, 'invoke', 'orchestrion:@langchain/core:RunnableSequence_invoke')
33
+ wrap(exports.RunnableSequence.prototype, 'batch', 'orchestrion:@langchain/core:RunnableSequence_batch')
34
+ }
41
35
  return exports
42
36
  })
43
37
 
@@ -46,51 +40,53 @@ for (const extension of extensions) {
46
40
  file: `dist/language_models/chat_models.${extension}`,
47
41
  versions: ['>=0.1']
48
42
  }, exports => {
49
- const BaseChatModel = exports.BaseChatModel
50
- shimmer.wrap(
51
- BaseChatModel.prototype,
52
- 'generate',
53
- generate => wrapLangChainPromise(generate, 'chat_model')
54
- )
43
+ if (extension === 'cjs') {
44
+ wrap(exports.BaseChatModel.prototype, 'generate', 'orchestrion:@langchain/core:BaseChatModel_generate')
45
+ }
55
46
  return exports
56
47
  })
57
48
 
58
49
  addHook({ name: '@langchain/core', file: `dist/language_models/llms.${extension}`, versions: ['>=0.1'] }, exports => {
59
- const BaseLLM = exports.BaseLLM
60
- shimmer.wrap(BaseLLM.prototype, 'generate', generate => wrapLangChainPromise(generate, 'llm'))
50
+ if (extension === 'cjs') {
51
+ wrap(exports.BaseLLM.prototype, 'generate', 'orchestrion:@langchain/core:BaseLLM_generate')
52
+ }
61
53
  return exports
62
54
  })
63
55
 
64
56
  addHook({ name: '@langchain/core', file: `dist/embeddings.${extension}`, versions: ['>=0.1'] }, exports => {
65
- // we cannot patch the prototype of the Embeddings class directly
66
- // this is because the "abstract class Embeddings" is transpiled from TypeScript to not include abstract functions
67
- // thus, we patch the exported class directly instead instead.
57
+ if (extension === 'cjs') {
58
+ shimmer.wrap(exports, 'Embeddings', Embeddings => {
59
+ return class extends Embeddings {
60
+ constructor (...args) {
61
+ super(...args)
62
+
63
+ const namespace = ['langchain', 'embeddings']
68
64
 
69
- shimmer.wrap(exports, 'Embeddings', Embeddings => {
70
- return class extends Embeddings {
71
- constructor (...args) {
72
- super(...args)
65
+ if (this.constructor.name === 'OpenAIEmbeddings') {
66
+ namespace.push('openai')
67
+ }
73
68
 
69
+ wrap(this, 'embedQuery', 'apm:@langchain/core:Embeddings_embedQuery', namespace)
70
+ wrap(this, 'embedDocuments', 'apm:@langchain/core:Embeddings_embedDocuments', namespace)
71
+ }
72
+ }
73
+ })
74
+ } else {
75
+ const channel = tracingChannel('orchestrion:@langchain/core:Embeddings_constructor')
76
+ channel.subscribe({
77
+ end (ctx) {
78
+ const { self } = ctx
74
79
  const namespace = ['langchain', 'embeddings']
75
80
 
76
- // when originally implemented, we only wrapped OpenAI embeddings
77
- // these embeddings had the resource name of `langchain.embeddings.openai.OpenAIEmbeddings`
78
- // we need to make sure `openai` is appended to the resource name until a new tracer major version
79
- if (this.constructor.name === 'OpenAIEmbeddings') {
81
+ if (self.constructor.name === 'OpenAIEmbeddings') {
80
82
  namespace.push('openai')
81
83
  }
82
84
 
83
- shimmer.wrap(this, 'embedQuery', embedQuery => wrapLangChainPromise(embedQuery, 'embedding', namespace))
84
- shimmer.wrap(this, 'embedDocuments',
85
- embedDocuments => wrapLangChainPromise(embedDocuments, 'embedding', namespace))
85
+ wrap(self, 'embedQuery', 'apm:@langchain/core:Embeddings_embedQuery', namespace)
86
+ wrap(self, 'embedDocuments', 'apm:@langchain/core:Embeddings_embedDocuments', namespace)
86
87
  }
87
-
88
- static [Symbol.hasInstance] (instance) {
89
- return instance instanceof Embeddings
90
- }
91
- }
92
- })
93
-
88
+ })
89
+ }
94
90
  return exports
95
91
  })
96
92
  }
@@ -417,7 +417,7 @@ addHook({
417
417
 
418
418
  this.on('test end', getOnTestEndHandler(config))
419
419
 
420
- this.on('retry', getOnTestRetryHandler())
420
+ this.on('retry', getOnTestRetryHandler(config))
421
421
 
422
422
  // If the hook passes, 'hook end' will be emitted. Otherwise, 'fail' will be emitted
423
423
  this.on('hook end', getOnHookEndHandler())
@@ -286,6 +286,9 @@ function getOnTestEndHandler (config) {
286
286
  }
287
287
 
288
288
  const isAttemptToFixRetry = test._ddIsAttemptToFix && testStatuses.length > 1
289
+ const isAtrRetry = config.isFlakyTestRetriesEnabled &&
290
+ !test._ddIsAttemptToFix &&
291
+ !test._ddIsEfdRetry
289
292
 
290
293
  // if there are afterEach to be run, we don't finish the test yet
291
294
  if (asyncResource && !getAfterEachHooks(test).length) {
@@ -296,7 +299,8 @@ function getOnTestEndHandler (config) {
296
299
  isLastRetry: getIsLastRetry(test),
297
300
  hasFailedAllRetries,
298
301
  attemptToFixPassed,
299
- isAttemptToFixRetry
302
+ isAttemptToFixRetry,
303
+ isAtrRetry
300
304
  })
301
305
  })
302
306
  }
@@ -364,14 +368,18 @@ function getOnFailHandler (isMain) {
364
368
  }
365
369
  }
366
370
 
367
- function getOnTestRetryHandler () {
371
+ function getOnTestRetryHandler (config) {
368
372
  return function (test, err) {
369
373
  const asyncResource = getTestAsyncResource(test)
370
374
  if (asyncResource) {
371
375
  const isFirstAttempt = test._currentRetry === 0
372
376
  const willBeRetried = test._currentRetry < test._retries
377
+ const isAtrRetry = !isFirstAttempt &&
378
+ config.isFlakyTestRetriesEnabled &&
379
+ !test._ddIsAttemptToFix &&
380
+ !test._ddIsEfdRetry
373
381
  asyncResource.runInAsyncScope(() => {
374
- testRetryCh.publish({ isFirstAttempt, err, willBeRetried, test })
382
+ testRetryCh.publish({ isFirstAttempt, err, willBeRetried, test, isAtrRetry })
375
383
  })
376
384
  }
377
385
  const key = getTestToArKey(test)
@@ -0,0 +1,5 @@
1
+ const path = require('path')
2
+ const fs = require('fs')
3
+
4
+ // TODO this needs to be inlined to prevent issues in bundling
5
+ module.exports = fs.readFileSync(path.join(__dirname, '../../orchestrion.yml'), 'utf8')
@@ -348,6 +348,10 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout, isMain
348
348
  if (isMainProcess) {
349
349
  const testResult = results[results.length - 1]
350
350
  const testAsyncResource = testToAr.get(test)
351
+ const isAtrRetry = testResult?.retry > 0 &&
352
+ isFlakyTestRetriesEnabled &&
353
+ !test._ddIsAttemptToFix &&
354
+ !test._ddIsEfdRetry
351
355
  testAsyncResource.runInAsyncScope(() => {
352
356
  testFinishCh.publish({
353
357
  testStatus,
@@ -361,7 +365,8 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout, isMain
361
365
  isQuarantined: test._ddIsQuarantined,
362
366
  isEfdRetry: test._ddIsEfdRetry,
363
367
  hasFailedAllRetries: test._ddHasFailedAllRetries,
364
- hasPassedAttemptToFixRetries: test._ddHasPassedAttemptToFixRetries
368
+ hasPassedAttemptToFixRetries: test._ddHasPassedAttemptToFixRetries,
369
+ isAtrRetry
365
370
  })
366
371
  })
367
372
  }
@@ -469,6 +474,11 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
469
474
 
470
475
  const isTimeout = status === 'timedOut'
471
476
  testEndHandler(test, annotations, STATUS_TO_TEST_STATUS[status], errors && errors[0], isTimeout, false)
477
+ const testResult = test.results[test.results.length - 1]
478
+ const isAtrRetry = testResult?.retry > 0 &&
479
+ isFlakyTestRetriesEnabled &&
480
+ !test._ddIsAttemptToFix &&
481
+ !test._ddIsEfdRetry
472
482
  // We want to send the ddProperties to the worker
473
483
  worker.process.send({
474
484
  type: 'ddProperties',
@@ -481,7 +491,8 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
481
491
  _ddIsNew: test._ddIsNew,
482
492
  _ddIsEfdRetry: test._ddIsEfdRetry,
483
493
  _ddHasFailedAllRetries: test._ddHasFailedAllRetries,
484
- _ddHasPassedAttemptToFixRetries: test._ddHasPassedAttemptToFixRetries
494
+ _ddHasPassedAttemptToFixRetries: test._ddHasPassedAttemptToFixRetries,
495
+ _ddIsAtrRetry: isAtrRetry
485
496
  }
486
497
  })
487
498
  })
@@ -961,6 +972,7 @@ addHook({
961
972
  isAttemptToFixRetry: test._ddIsAttemptToFixRetry,
962
973
  hasFailedAllRetries: test._ddHasFailedAllRetries,
963
974
  hasPassedAttemptToFixRetries: test._ddHasPassedAttemptToFixRetries,
975
+ isAtrRetry: test._ddIsAtrRetry,
964
976
  onDone
965
977
  })
966
978
  })
@@ -82,7 +82,8 @@ function getProvidedContext () {
82
82
  isKnownTestsEnabled: false,
83
83
  isTestManagementTestsEnabled: false,
84
84
  testManagementAttemptToFixRetries: 0,
85
- testManagementTests: {}
85
+ testManagementTests: {},
86
+ isFlakyTestRetriesEnabled: false
86
87
  }
87
88
  }
88
89
  }
@@ -466,7 +467,8 @@ addHook({
466
467
  isEarlyFlakeDetectionEnabled,
467
468
  isDiEnabled,
468
469
  isTestManagementTestsEnabled,
469
- testManagementTests
470
+ testManagementTests,
471
+ isFlakyTestRetriesEnabled
470
472
  } = getProvidedContext()
471
473
 
472
474
  if (isKnownTestsEnabled) {
@@ -566,6 +568,11 @@ addHook({
566
568
  }
567
569
  }
568
570
 
571
+ const isRetryReasonAtr = numAttempt > 0 &&
572
+ isFlakyTestRetriesEnabled &&
573
+ !isRetryReasonAttemptToFix &&
574
+ !isRetryReasonEfd
575
+
569
576
  const asyncResource = new AsyncResource('bound-anonymous-fn')
570
577
  taskToAsync.set(task, asyncResource)
571
578
 
@@ -580,7 +587,8 @@ addHook({
580
587
  mightHitProbe: isDiEnabled && numAttempt > 0,
581
588
  isAttemptToFix: attemptToFixTasks.has(task),
582
589
  isDisabled: disabledTasks.has(task),
583
- isQuarantined
590
+ isQuarantined,
591
+ isRetryReasonAtr
584
592
  })
585
593
  })
586
594
  return onBeforeTryTask.apply(this, arguments)
@@ -33,7 +33,8 @@ const {
33
33
  TEST_MANAGEMENT_IS_DISABLED,
34
34
  TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX,
35
35
  TEST_HAS_FAILED_ALL_RETRIES,
36
- TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED
36
+ TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED,
37
+ TEST_RETRY_REASON_TYPES
37
38
  } = require('../../dd-trace/src/plugins/util/test')
38
39
  const { RESOURCE_NAME } = require('../../../ext/tags')
39
40
  const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
@@ -252,11 +253,16 @@ class CucumberPlugin extends CiPlugin {
252
253
  }
253
254
  })
254
255
 
255
- this.addSub('ci:cucumber:test:retry', ({ isFirstAttempt, error }) => {
256
+ this.addSub('ci:cucumber:test:retry', ({ isFirstAttempt, error, isAtrRetry }) => {
256
257
  const store = storage('legacy').getStore()
257
258
  const span = store.span
258
259
  if (!isFirstAttempt) {
259
260
  span.setTag(TEST_IS_RETRY, 'true')
261
+ if (isAtrRetry) {
262
+ span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atr)
263
+ } else {
264
+ span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.ext)
265
+ }
260
266
  }
261
267
  span.setTag('error', error)
262
268
  if (isFirstAttempt && this.di && error && this.libraryConfig?.isDiEnabled) {
@@ -347,7 +353,7 @@ class CucumberPlugin extends CiPlugin {
347
353
  span.setTag(TEST_IS_NEW, 'true')
348
354
  if (isEfdRetry) {
349
355
  span.setTag(TEST_IS_RETRY, 'true')
350
- span.setTag(TEST_RETRY_REASON, 'efd')
356
+ span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
351
357
  }
352
358
  }
353
359
 
@@ -363,6 +369,7 @@ class CucumberPlugin extends CiPlugin {
363
369
 
364
370
  if (isFlakyRetry > 0) {
365
371
  span.setTag(TEST_IS_RETRY, 'true')
372
+ span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atr)
366
373
  }
367
374
 
368
375
  if (hasFailedAllRetries) {
@@ -375,7 +382,7 @@ class CucumberPlugin extends CiPlugin {
375
382
 
376
383
  if (isAttemptToFixRetry) {
377
384
  span.setTag(TEST_IS_RETRY, 'true')
378
- span.setTag(TEST_RETRY_REASON, 'attempt_to_fix')
385
+ span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atf)
379
386
  if (hasPassedAllRetries) {
380
387
  span.setTag(TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED, 'true')
381
388
  }
@@ -40,7 +40,8 @@ const {
40
40
  TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX,
41
41
  TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED,
42
42
  TEST_HAS_FAILED_ALL_RETRIES,
43
- getLibraryCapabilitiesTags
43
+ getLibraryCapabilitiesTags,
44
+ TEST_RETRY_REASON_TYPES
44
45
  } = require('../../dd-trace/src/plugins/util/test')
45
46
  const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
46
47
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
@@ -229,6 +230,7 @@ class CypressPlugin {
229
230
  this.isTestsSkipped = false
230
231
  this.isSuitesSkippingEnabled = false
231
232
  this.isCodeCoverageEnabled = false
233
+ this.isFlakyTestRetriesEnabled = false
232
234
  this.isEarlyFlakeDetectionEnabled = false
233
235
  this.isKnownTestsEnabled = false
234
236
  this.earlyFlakeDetectionNumRetries = 0
@@ -278,6 +280,7 @@ class CypressPlugin {
278
280
  this.earlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
279
281
  this.isKnownTestsEnabled = isKnownTestsEnabled
280
282
  if (isFlakyTestRetriesEnabled) {
283
+ this.isFlakyTestRetriesEnabled = true
281
284
  this.cypressConfig.retries.runMode = flakyTestRetriesCount
282
285
  }
283
286
  this.isTestManagementTestsEnabled = isTestManagementEnabled
@@ -654,10 +657,18 @@ class CypressPlugin {
654
657
  let cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.state]
655
658
  if (cypressTest.attempts && cypressTest.attempts[attemptIndex]) {
656
659
  cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.attempts[attemptIndex].state]
660
+ const isAtrRetry = attemptIndex > 0 &&
661
+ this.isFlakyTestRetriesEnabled &&
662
+ !finishedTest.isAttemptToFix &&
663
+ !finishedTest.isEfdRetry
657
664
  if (attemptIndex > 0) {
658
665
  finishedTest.testSpan.setTag(TEST_IS_RETRY, 'true')
659
666
  if (finishedTest.isEfdRetry) {
660
- finishedTest.testSpan.setTag(TEST_RETRY_REASON, 'efd')
667
+ finishedTest.testSpan.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
668
+ } else if (isAtrRetry) {
669
+ finishedTest.testSpan.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atr)
670
+ } else {
671
+ finishedTest.testSpan.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.ext)
661
672
  }
662
673
  }
663
674
  }
@@ -816,14 +827,14 @@ class CypressPlugin {
816
827
  this.activeTestSpan.setTag(TEST_IS_NEW, 'true')
817
828
  if (isEfdRetry) {
818
829
  this.activeTestSpan.setTag(TEST_IS_RETRY, 'true')
819
- this.activeTestSpan.setTag(TEST_RETRY_REASON, 'efd')
830
+ this.activeTestSpan.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
820
831
  }
821
832
  }
822
833
  if (isAttemptToFix) {
823
834
  this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX, 'true')
824
835
  if (testStatuses.length > 1) {
825
836
  this.activeTestSpan.setTag(TEST_IS_RETRY, 'true')
826
- this.activeTestSpan.setTag(TEST_RETRY_REASON, 'attempt_to_fix')
837
+ this.activeTestSpan.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atf)
827
838
  }
828
839
  const isLastAttempt = testStatuses.length === this.testManagementAttemptToFixRetries + 1
829
840
  if (isLastAttempt) {
@@ -840,7 +851,8 @@ class CypressPlugin {
840
851
  testStatus,
841
852
  finishTime: this.activeTestSpan._getTime(), // we store the finish time here
842
853
  testSpan: this.activeTestSpan,
843
- isEfdRetry
854
+ isEfdRetry,
855
+ isAttemptToFix
844
856
  }
845
857
  if (this.finishedTestsByFile[testSuite]) {
846
858
  this.finishedTestsByFile[testSuite].push(finishedTest)
@@ -30,7 +30,8 @@ const {
30
30
  TEST_MANAGEMENT_IS_DISABLED,
31
31
  TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX,
32
32
  TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED,
33
- TEST_HAS_FAILED_ALL_RETRIES
33
+ TEST_HAS_FAILED_ALL_RETRIES,
34
+ TEST_RETRY_REASON_TYPES
34
35
  } = require('../../dd-trace/src/plugins/util/test')
35
36
  const { COMPONENT } = require('../../dd-trace/src/constants')
36
37
  const id = require('../../dd-trace/src/id')
@@ -344,7 +345,8 @@ class JestPlugin extends CiPlugin {
344
345
  status,
345
346
  testStartLine,
346
347
  attemptToFixPassed,
347
- failedAllTests
348
+ failedAllTests,
349
+ isAtrRetry
348
350
  }) => {
349
351
  const span = storage('legacy').getStore().span
350
352
  span.setTag(TEST_STATUS, status)
@@ -357,6 +359,10 @@ class JestPlugin extends CiPlugin {
357
359
  if (failedAllTests) {
358
360
  span.setTag(TEST_HAS_FAILED_ALL_RETRIES, 'true')
359
361
  }
362
+ if (isAtrRetry) {
363
+ span.setTag(TEST_IS_RETRY, 'true')
364
+ span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atr)
365
+ }
360
366
 
361
367
  const spanTags = span.context()._tags
362
368
  this.telemetry.ciVisEvent(
@@ -447,7 +453,7 @@ class JestPlugin extends CiPlugin {
447
453
 
448
454
  if (isAttemptToFixRetry) {
449
455
  extraTags[TEST_IS_RETRY] = 'true'
450
- extraTags[TEST_RETRY_REASON] = 'attempt_to_fix'
456
+ extraTags[TEST_RETRY_REASON] = TEST_RETRY_REASON_TYPES.atf
451
457
  }
452
458
 
453
459
  if (isDisabled) {
@@ -462,12 +468,13 @@ class JestPlugin extends CiPlugin {
462
468
  extraTags[TEST_IS_NEW] = 'true'
463
469
  if (isEfdRetry) {
464
470
  extraTags[TEST_IS_RETRY] = 'true'
465
- extraTags[TEST_RETRY_REASON] = 'efd'
471
+ extraTags[TEST_RETRY_REASON] = TEST_RETRY_REASON_TYPES.efd
466
472
  }
467
473
  }
468
474
 
469
475
  if (isJestRetry) {
470
476
  extraTags[TEST_IS_RETRY] = 'true'
477
+ extraTags[TEST_RETRY_REASON] = TEST_RETRY_REASON_TYPES.ext
471
478
  }
472
479
 
473
480
  return super.startTestSpan(name, suite, this.testSuiteSpan, extraTags)