dd-trace 5.43.0 → 5.44.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 (51) hide show
  1. package/package.json +4 -4
  2. package/packages/datadog-instrumentations/src/cucumber.js +61 -23
  3. package/packages/datadog-instrumentations/src/dd-trace-api.js +7 -0
  4. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  5. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  6. package/packages/datadog-instrumentations/src/jest.js +134 -48
  7. package/packages/datadog-instrumentations/src/mocha/main.js +20 -4
  8. package/packages/datadog-instrumentations/src/mocha/utils.js +89 -30
  9. package/packages/datadog-instrumentations/src/mocha/worker.js +3 -1
  10. package/packages/datadog-instrumentations/src/playwright.js +97 -17
  11. package/packages/datadog-instrumentations/src/router.js +1 -0
  12. package/packages/datadog-instrumentations/src/tedious.js +13 -10
  13. package/packages/datadog-instrumentations/src/vitest.js +77 -17
  14. package/packages/datadog-plugin-cucumber/src/index.js +24 -1
  15. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +69 -20
  16. package/packages/datadog-plugin-cypress/src/support.js +39 -10
  17. package/packages/datadog-plugin-google-cloud-vertexai/src/index.js +8 -186
  18. package/packages/datadog-plugin-google-cloud-vertexai/src/tracing.js +186 -0
  19. package/packages/datadog-plugin-google-cloud-vertexai/src/utils.js +19 -0
  20. package/packages/datadog-plugin-jest/src/index.js +38 -5
  21. package/packages/datadog-plugin-mocha/src/index.js +28 -5
  22. package/packages/datadog-plugin-playwright/src/index.js +22 -2
  23. package/packages/datadog-plugin-tedious/src/index.js +14 -9
  24. package/packages/datadog-plugin-vitest/src/index.js +46 -14
  25. package/packages/dd-trace/src/appsec/blocking.js +2 -0
  26. package/packages/dd-trace/src/appsec/graphql.js +3 -1
  27. package/packages/dd-trace/src/appsec/reporter.js +13 -8
  28. package/packages/dd-trace/src/appsec/telemetry/common.js +6 -3
  29. package/packages/dd-trace/src/appsec/telemetry/index.js +20 -4
  30. package/packages/dd-trace/src/appsec/telemetry/waf.js +29 -9
  31. package/packages/dd-trace/src/appsec/waf/waf_manager.js +16 -7
  32. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -2
  33. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +3 -1
  34. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +4 -2
  35. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  36. package/packages/dd-trace/src/llmobs/noop.js +3 -3
  37. package/packages/dd-trace/src/llmobs/plugins/base.js +1 -1
  38. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +2 -1
  39. package/packages/dd-trace/src/llmobs/plugins/vertexai.js +196 -0
  40. package/packages/dd-trace/src/llmobs/sdk.js +2 -0
  41. package/packages/dd-trace/src/llmobs/span_processor.js +6 -0
  42. package/packages/dd-trace/src/llmobs/tagger.js +8 -2
  43. package/packages/dd-trace/src/llmobs/telemetry.js +82 -1
  44. package/packages/dd-trace/src/llmobs/writers/spans/base.js +10 -1
  45. package/packages/dd-trace/src/plugin_manager.js +0 -3
  46. package/packages/dd-trace/src/plugins/ci_plugin.js +16 -26
  47. package/packages/dd-trace/src/plugins/database.js +4 -4
  48. package/packages/dd-trace/src/plugins/plugin.js +2 -0
  49. package/packages/dd-trace/src/plugins/util/test.js +62 -1
  50. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +7 -6
  51. package/packages/dd-trace/src/telemetry/send-data.js +5 -1
@@ -0,0 +1,196 @@
1
+ 'use strict'
2
+
3
+ const LLMObsPlugin = require('./base')
4
+ const {
5
+ extractModel,
6
+ extractSystemInstructions
7
+ } = require('../../../../datadog-plugin-google-cloud-vertexai/src/utils')
8
+
9
+ class VertexAILLMObsPlugin extends LLMObsPlugin {
10
+ static get id () { return 'vertexai' } // used for llmobs telemetry
11
+ static get prefix () {
12
+ return 'tracing:apm:vertexai:request'
13
+ }
14
+
15
+ getLLMObsSpanRegisterOptions (ctx) {
16
+ const history = ctx.instance?.historyInternal || []
17
+ ctx.history = history
18
+
19
+ return {
20
+ kind: 'llm',
21
+ modelName: extractModel(ctx.instance),
22
+ modelProvider: 'google',
23
+ name: ctx.resource
24
+ }
25
+ }
26
+
27
+ setLLMObsTags (ctx) {
28
+ const span = ctx.currentStore?.span
29
+ if (!span) return
30
+
31
+ const { instance, result, request } = ctx
32
+ const history = ctx.history || []
33
+ const systemInstructions = extractSystemInstructions(instance)
34
+
35
+ const metadata = getMetadata(instance)
36
+ const inputMessages = extractInputMessages(request, history, systemInstructions)
37
+ const outputMessages = extractOutputMessages(result)
38
+ const metrics = extractMetrics(result)
39
+
40
+ this._tagger.tagLLMIO(span, inputMessages, outputMessages)
41
+ this._tagger.tagMetadata(span, metadata)
42
+ this._tagger.tagMetrics(span, metrics)
43
+ }
44
+ }
45
+
46
+ function getMetadata (instance) {
47
+ const metadata = {}
48
+
49
+ const modelConfig = instance.generationConfig
50
+ if (!modelConfig) return metadata
51
+
52
+ for (const [parameter, parameterKey] of [
53
+ ['temperature', 'temperature'],
54
+ ['maxOutputTokens', 'max_output_tokens'],
55
+ ['candidateCount', 'candidate_count'],
56
+ ['topP', 'top_p'],
57
+ ['topK', 'top_k']
58
+ ]) {
59
+ if (modelConfig[parameter]) {
60
+ metadata[parameterKey] = modelConfig[parameter]
61
+ }
62
+ }
63
+
64
+ return metadata
65
+ }
66
+
67
+ function extractInputMessages (request, history, systemInstructions) {
68
+ const contents = typeof request === 'string' || Array.isArray(request) ? request : request.contents
69
+ const messages = []
70
+
71
+ if (systemInstructions) {
72
+ for (const instruction of systemInstructions) {
73
+ messages.push({ content: instruction || '', role: 'system' })
74
+ }
75
+ }
76
+
77
+ for (const content of history) {
78
+ messages.push(...extractMessagesFromContent(content))
79
+ }
80
+
81
+ if (typeof contents === 'string') {
82
+ messages.push({ content: contents })
83
+ return messages
84
+ }
85
+
86
+ if (isPart(contents)) {
87
+ messages.push(extractMessageFromPart(contents))
88
+ return messages
89
+ }
90
+
91
+ if (!Array.isArray(contents)) {
92
+ messages.push({
93
+ content: '[Non-array content object: ' +
94
+ `${(typeof contents.toString === 'function' ? contents.toString() : String(contents))}]`
95
+ })
96
+ return messages
97
+ }
98
+
99
+ for (const content of contents) {
100
+ if (typeof content === 'string') {
101
+ messages.push({ content })
102
+ continue
103
+ }
104
+
105
+ if (isPart(content)) {
106
+ messages.push(extractMessageFromPart(content))
107
+ continue
108
+ }
109
+
110
+ messages.push(...extractMessagesFromContent(content))
111
+ }
112
+
113
+ return messages
114
+ }
115
+
116
+ function extractOutputMessages (result) {
117
+ if (!result) return [{ content: '' }]
118
+ const { response } = result
119
+
120
+ if (!response) return [{ content: '' }]
121
+
122
+ const outputMessages = []
123
+ const candidates = response.candidates || []
124
+ for (const candidate of candidates) {
125
+ const content = candidate.content || ''
126
+ outputMessages.push(...extractMessagesFromContent(content))
127
+ }
128
+
129
+ return outputMessages
130
+ }
131
+
132
+ function extractMessagesFromContent (content) {
133
+ const messages = []
134
+
135
+ const role = content.role || ''
136
+ const parts = content.parts || []
137
+ if (parts == null || parts.length === 0 || !Array.isArray(parts)) {
138
+ const message = {
139
+ content:
140
+ `[Non-text content object: ${(typeof content.toString === 'function' ? content.toString() : String(content))}]`
141
+ }
142
+ if (role) message.role = role
143
+ messages.push(message)
144
+ return messages
145
+ }
146
+
147
+ for (const part of parts) {
148
+ const message = extractMessageFromPart(part, role)
149
+ messages.push(message)
150
+ }
151
+
152
+ return messages
153
+ }
154
+
155
+ function extractMessageFromPart (part, role) {
156
+ const text = part.text || ''
157
+ const functionCall = part.functionCall
158
+ const functionResponse = part.functionResponse
159
+
160
+ const message = { content: text }
161
+ if (role) message.role = role
162
+ if (functionCall) {
163
+ message.toolCalls = [{
164
+ name: functionCall.name,
165
+ arguments: functionCall.args
166
+ }]
167
+ }
168
+ if (functionResponse) {
169
+ message.content = `[tool result: ${functionResponse.response}]`
170
+ }
171
+
172
+ return message
173
+ }
174
+
175
+ function extractMetrics (result) {
176
+ if (!result) return {}
177
+ const { response } = result
178
+
179
+ if (!response) return {}
180
+
181
+ const tokenCounts = response.usageMetadata
182
+ const metrics = {}
183
+ if (tokenCounts) {
184
+ metrics.inputTokens = tokenCounts.promptTokenCount
185
+ metrics.outputTokens = tokenCounts.candidatesTokenCount
186
+ metrics.totalTokens = tokenCounts.totalTokenCount
187
+ }
188
+
189
+ return metrics
190
+ }
191
+
192
+ function isPart (part) {
193
+ return part.text || part.functionCall || part.functionResponse
194
+ }
195
+
196
+ module.exports = VertexAILLMObsPlugin
@@ -430,6 +430,7 @@ class LLMObs extends NoopLLMObs {
430
430
  modelProvider,
431
431
  sessionId,
432
432
  mlApp,
433
+ _decorator,
433
434
  ...spanOptions
434
435
  } = options
435
436
 
@@ -438,6 +439,7 @@ class LLMObs extends NoopLLMObs {
438
439
  modelName,
439
440
  modelProvider,
440
441
  sessionId,
442
+ _decorator,
441
443
  spanOptions
442
444
  }
443
445
  }
@@ -7,6 +7,7 @@ const {
7
7
  METADATA,
8
8
  INPUT_MESSAGES,
9
9
  INPUT_VALUE,
10
+ INTEGRATION,
10
11
  OUTPUT_MESSAGES,
11
12
  INPUT_DOCUMENTS,
12
13
  OUTPUT_DOCUMENTS,
@@ -26,6 +27,8 @@ const {
26
27
  ERROR_STACK
27
28
  } = require('../constants')
28
29
 
30
+ const telemetry = require('./telemetry')
31
+
29
32
  const LLMObsTagger = require('./tagger')
30
33
 
31
34
  const tracerVersion = require('../../../../package.json').version
@@ -48,6 +51,7 @@ class LLMObsSpanProcessor {
48
51
 
49
52
  try {
50
53
  const formattedEvent = this.format(span)
54
+ telemetry.incrementLLMObsSpanFinishedCount(span)
51
55
  this._writer.append(formattedEvent)
52
56
  } catch (e) {
53
57
  // this should be a rare case
@@ -186,6 +190,8 @@ class LLMObsSpanProcessor {
186
190
  const errType = span.context()._tags[ERROR_TYPE] || error?.name
187
191
  if (errType) tags.error_type = errType
188
192
  if (sessionId) tags.session_id = sessionId
193
+ const integration = LLMObsTagger.tagMap.get(span)?.[INTEGRATION]
194
+ if (integration) tags.integration = integration
189
195
  const existingTags = LLMObsTagger.tagMap.get(span)?.[TAGS] || {}
190
196
  if (existingTags) tags = { ...tags, ...existingTags }
191
197
  return Object.entries(tags).map(([key, value]) => `${key}:${value ?? ''}`)
@@ -22,7 +22,9 @@ const {
22
22
  ROOT_PARENT_ID,
23
23
  INPUT_TOKENS_METRIC_KEY,
24
24
  OUTPUT_TOKENS_METRIC_KEY,
25
- TOTAL_TOKENS_METRIC_KEY
25
+ TOTAL_TOKENS_METRIC_KEY,
26
+ INTEGRATION,
27
+ DECORATOR
26
28
  } = require('./constants/tags')
27
29
 
28
30
  // global registry of LLMObs spans
@@ -51,7 +53,9 @@ class LLMObsTagger {
51
53
  mlApp,
52
54
  parent,
53
55
  kind,
54
- name
56
+ name,
57
+ integration,
58
+ _decorator
55
59
  } = {}) {
56
60
  if (!this._config.llmobs.enabled) return
57
61
  if (!kind) return // do not register it in the map if it doesn't have an llmobs span kind
@@ -66,6 +70,8 @@ class LLMObsTagger {
66
70
 
67
71
  sessionId = sessionId || registry.get(parent)?.[SESSION_ID]
68
72
  if (sessionId) this._setTag(span, SESSION_ID, sessionId)
73
+ if (integration) this._setTag(span, INTEGRATION, integration)
74
+ if (_decorator) this._setTag(span, DECORATOR, _decorator)
69
75
 
70
76
  if (!mlApp) mlApp = registry.get(parent)?.[ML_APP] || this._config.llmobs.mlApp
71
77
  this._setTag(span, ML_APP, mlApp)
@@ -1,12 +1,93 @@
1
1
  'use strict'
2
2
 
3
+ const {
4
+ SPAN_KIND,
5
+ MODEL_PROVIDER,
6
+ PARENT_ID_KEY,
7
+ SESSION_ID,
8
+ ROOT_PARENT_ID,
9
+ INTEGRATION,
10
+ DECORATOR
11
+ } = require('./constants/tags')
12
+
13
+ const ERROR_TYPE = require('../constants')
14
+
3
15
  const telemetryMetrics = require('../telemetry/metrics')
16
+
17
+ const LLMObsTagger = require('./tagger')
18
+
4
19
  const llmobsMetrics = telemetryMetrics.manager.namespace('mlobs')
5
20
 
21
+ function extractIntegrationFromTags (tags) {
22
+ if (!Array.isArray(tags)) return null
23
+ const integrationTag = tags.find(tag => tag.startsWith('integration:'))
24
+ if (!integrationTag) return null
25
+ return integrationTag.split(':')[1] || null
26
+ }
27
+
28
+ function extractTagsFromSpanEvent (event) {
29
+ const spanKind = event.meta?.['span.kind'] || ''
30
+ const integration = extractIntegrationFromTags(event.tags)
31
+ const error = event.status === 'error'
32
+ const autoinstrumented = integration != null
33
+
34
+ return {
35
+ span_kind: spanKind,
36
+ autoinstrumented: Number(autoinstrumented),
37
+ error: error ? 1 : 0,
38
+ integration: integration || 'N/A'
39
+ }
40
+ }
41
+
6
42
  function incrementLLMObsSpanStartCount (tags, value = 1) {
7
43
  llmobsMetrics.count('span.start', tags).inc(value)
8
44
  }
9
45
 
46
+ function incrementLLMObsSpanFinishedCount (span, value = 1) {
47
+ const mlObsTags = LLMObsTagger.tagMap.get(span)
48
+ const spanTags = span.context()._tags
49
+
50
+ const isRootSpan = mlObsTags[PARENT_ID_KEY] === ROOT_PARENT_ID
51
+ const hasSessionId = mlObsTags[SESSION_ID] != null
52
+ const integration = mlObsTags[INTEGRATION]
53
+ const autoInstrumented = integration != null
54
+ const decorator = !!mlObsTags[DECORATOR]
55
+ const spanKind = mlObsTags[SPAN_KIND]
56
+ const modelProvider = mlObsTags[MODEL_PROVIDER]
57
+ const error = spanTags.error || spanTags[ERROR_TYPE]
58
+
59
+ const tags = {
60
+ autoinstrumented: Number(autoInstrumented),
61
+ has_session_id: Number(hasSessionId),
62
+ is_root_span: Number(isRootSpan),
63
+ span_kind: spanKind,
64
+ integration: integration || 'N/A',
65
+ error: error ? 1 : 0
66
+ }
67
+ if (!autoInstrumented) {
68
+ tags.decorator = Number(decorator)
69
+ }
70
+ if (modelProvider) {
71
+ tags.model_provider = modelProvider
72
+ }
73
+
74
+ llmobsMetrics.count('span.finished', tags).inc(value)
75
+ }
76
+
77
+ function recordLLMObsRawSpanSize (event, rawEventSize) {
78
+ const tags = extractTagsFromSpanEvent(event)
79
+ llmobsMetrics.distribution('span.raw_size', tags).track(rawEventSize)
80
+ }
81
+
82
+ function recordLLMObsSpanSize (event, eventSize, shouldTruncate) {
83
+ const tags = extractTagsFromSpanEvent(event)
84
+ tags.truncated = Number(shouldTruncate)
85
+ llmobsMetrics.distribution('span.size', tags).track(eventSize)
86
+ }
87
+
10
88
  module.exports = {
11
- incrementLLMObsSpanStartCount
89
+ incrementLLMObsSpanStartCount,
90
+ incrementLLMObsSpanFinishedCount,
91
+ recordLLMObsRawSpanSize,
92
+ recordLLMObsSpanSize
12
93
  }
@@ -4,6 +4,7 @@ const { EVP_EVENT_SIZE_LIMIT, EVP_PAYLOAD_SIZE_LIMIT } = require('../../constant
4
4
  const { DROPPED_VALUE_TEXT } = require('../../constants/text')
5
5
  const { DROPPED_IO_COLLECTION_ERROR } = require('../../constants/tags')
6
6
  const BaseWriter = require('../base')
7
+ const telemetry = require('../../telemetry')
7
8
  const logger = require('../../../log')
8
9
 
9
10
  const tracerVersion = require('../../../../../../package.json').version
@@ -18,11 +19,19 @@ class LLMObsSpanWriter extends BaseWriter {
18
19
 
19
20
  append (event) {
20
21
  const eventSizeBytes = Buffer.from(JSON.stringify(event)).byteLength
21
- if (eventSizeBytes > EVP_EVENT_SIZE_LIMIT) {
22
+ telemetry.recordLLMObsRawSpanSize(event, eventSizeBytes)
23
+
24
+ const shouldTruncate = eventSizeBytes > EVP_EVENT_SIZE_LIMIT
25
+ let processedEventSizeBytes = eventSizeBytes
26
+
27
+ if (shouldTruncate) {
22
28
  logger.warn(`Dropping event input/output because its size (${eventSizeBytes}) exceeds the 1MB event size limit`)
23
29
  event = this._truncateSpanEvent(event)
30
+ processedEventSizeBytes = Buffer.from(JSON.stringify(event)).byteLength
24
31
  }
25
32
 
33
+ telemetry.recordLLMObsSpanSize(event, processedEventSizeBytes, shouldTruncate)
34
+
26
35
  if (this._bufferSize + eventSizeBytes > EVP_PAYLOAD_SIZE_LIMIT) {
27
36
  logger.debug('Flusing queue because queing next event will exceed EvP payload limit')
28
37
  this.flush()
@@ -28,9 +28,6 @@ loadChannel.subscribe(({ name }) => {
28
28
  maybeEnable(plugins[name])
29
29
  })
30
30
 
31
- // Always enabled
32
- maybeEnable(require('../../datadog-plugin-dd-trace-api/src'))
33
-
34
31
  function maybeEnable (Plugin) {
35
32
  if (!Plugin || typeof Plugin !== 'function') return
36
33
  if (!pluginClasses[Plugin.id]) {
@@ -28,9 +28,7 @@ const {
28
28
  DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX,
29
29
  DI_DEBUG_ERROR_FILE_SUFFIX,
30
30
  DI_DEBUG_ERROR_LINE_SUFFIX,
31
- DD_CAPABILITIES_EARLY_FLAKE_DETECTION,
32
- DD_CAPABILITIES_AUTO_TEST_RETRIES,
33
- DD_CAPABILITIES_TEST_IMPACT_ANALYSIS
31
+ getLibraryCapabilitiesTags
34
32
  } = require('./util/test')
35
33
  const Plugin = require('./plugin')
36
34
  const { COMPONENT } = require('../constants')
@@ -41,23 +39,17 @@ const {
41
39
  TELEMETRY_EVENT_CREATED,
42
40
  TELEMETRY_ITR_SKIPPED
43
41
  } = require('../ci-visibility/telemetry')
44
- const { CI_PROVIDER_NAME, GIT_REPOSITORY_URL, GIT_COMMIT_SHA, GIT_BRANCH, CI_WORKSPACE_PATH } = require('./util/tags')
42
+ const {
43
+ CI_PROVIDER_NAME,
44
+ GIT_REPOSITORY_URL,
45
+ GIT_COMMIT_SHA,
46
+ GIT_BRANCH,
47
+ CI_WORKSPACE_PATH,
48
+ GIT_COMMIT_MESSAGE
49
+ } = require('./util/tags')
45
50
  const { OS_VERSION, OS_PLATFORM, OS_ARCHITECTURE, RUNTIME_NAME, RUNTIME_VERSION } = require('./util/env')
46
51
  const getDiClient = require('../ci-visibility/dynamic-instrumentation')
47
52
 
48
- const UNSUPPORTED_TIA_FRAMEWORKS = ['playwright', 'vitest']
49
- const UNSUPPORTED_TIA_FRAMEWORKS_PARALLEL_MODE = ['cucumber', 'mocha']
50
-
51
- function isTiaSupported (testFramework, isParallel) {
52
- if (UNSUPPORTED_TIA_FRAMEWORKS.includes(testFramework)) {
53
- return false
54
- }
55
- if (isParallel && UNSUPPORTED_TIA_FRAMEWORKS_PARALLEL_MODE.includes(testFramework)) {
56
- return false
57
- }
58
- return true
59
- }
60
-
61
53
  module.exports = class CiPlugin extends Plugin {
62
54
  constructor (...args) {
63
55
  super(...args)
@@ -75,17 +67,13 @@ module.exports = class CiPlugin extends Plugin {
75
67
  } else {
76
68
  this.libraryConfig = libraryConfig
77
69
  }
78
- const { isItrEnabled, isEarlyFlakeDetectionEnabled, isFlakyTestRetriesEnabled } = this.libraryConfig || {}
70
+
71
+ const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, isParallel)
79
72
  const metadataTags = {
80
73
  test: {
81
- [DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: isItrEnabled ? 'true' : 'false',
82
- [DD_CAPABILITIES_EARLY_FLAKE_DETECTION]: isEarlyFlakeDetectionEnabled ? 'true' : 'false',
83
- [DD_CAPABILITIES_AUTO_TEST_RETRIES]: isFlakyTestRetriesEnabled ? 'true' : 'false'
74
+ ...libraryCapabilitiesTags
84
75
  }
85
76
  }
86
- if (!isTiaSupported(this.constructor.id, isParallel)) {
87
- metadataTags.test[DD_CAPABILITIES_TEST_IMPACT_ANALYSIS] = undefined
88
- }
89
77
  this.tracer._exporter.addMetadataTags(metadataTags)
90
78
  onDone({ err, libraryConfig })
91
79
  })
@@ -251,7 +239,8 @@ module.exports = class CiPlugin extends Plugin {
251
239
  [RUNTIME_VERSION]: runtimeVersion,
252
240
  [GIT_BRANCH]: branch,
253
241
  [CI_PROVIDER_NAME]: ciProviderName,
254
- [CI_WORKSPACE_PATH]: repositoryRoot
242
+ [CI_WORKSPACE_PATH]: repositoryRoot,
243
+ [GIT_COMMIT_MESSAGE]: commitMessage
255
244
  } = this.testEnvironmentMetadata
256
245
 
257
246
  this.repositoryRoot = repositoryRoot || process.cwd()
@@ -269,7 +258,8 @@ module.exports = class CiPlugin extends Plugin {
269
258
  runtimeName,
270
259
  runtimeVersion,
271
260
  branch,
272
- testLevel: 'suite'
261
+ testLevel: 'suite',
262
+ commitMessage
273
263
  }
274
264
  }
275
265
 
@@ -63,7 +63,7 @@ class DatabasePlugin extends StoragePlugin {
63
63
  return tracerService
64
64
  }
65
65
 
66
- createDbmComment (span, serviceName, isPreparedStatement = false) {
66
+ createDbmComment (span, serviceName, disableFullMode = false) {
67
67
  const mode = this.config.dbmPropagationMode
68
68
  const dbmService = this.getDbmServiceName(span, serviceName)
69
69
 
@@ -73,7 +73,7 @@ class DatabasePlugin extends StoragePlugin {
73
73
 
74
74
  const servicePropagation = this.createDBMPropagationCommentService(dbmService, span)
75
75
 
76
- if (isPreparedStatement || mode === 'service') {
76
+ if (disableFullMode || mode === 'service') {
77
77
  return servicePropagation
78
78
  } else if (mode === 'full') {
79
79
  span.setTag('_dd.dbm_trace_injected', 'true')
@@ -82,8 +82,8 @@ class DatabasePlugin extends StoragePlugin {
82
82
  }
83
83
  }
84
84
 
85
- injectDbmQuery (span, query, serviceName, isPreparedStatement = false) {
86
- const dbmTraceComment = this.createDbmComment(span, serviceName, isPreparedStatement)
85
+ injectDbmQuery (span, query, serviceName, disableFullMode = false) {
86
+ const dbmTraceComment = this.createDbmComment(span, serviceName, disableFullMode)
87
87
 
88
88
  if (!dbmTraceComment) {
89
89
  return query
@@ -18,10 +18,12 @@ class Subscription {
18
18
  }
19
19
 
20
20
  enable () {
21
+ // TODO: Once Node.js v18.6.0 is no longer supported, we should use `dc.subscribe(event, handler)` instead
21
22
  this._channel.subscribe(this._handler)
22
23
  }
23
24
 
24
25
  disable () {
26
+ // TODO: Once Node.js v18.6.0 is no longer supported, we should use `dc.unsubscribe(event, handler)` instead
25
27
  this._channel.unsubscribe(this._handler)
26
28
  }
27
29
  }
@@ -58,6 +58,7 @@ const TEST_IS_RETRY = 'test.is_retry'
58
58
  const TEST_EARLY_FLAKE_ENABLED = 'test.early_flake.enabled'
59
59
  const TEST_EARLY_FLAKE_ABORT_REASON = 'test.early_flake.abort_reason'
60
60
  const TEST_RETRY_REASON = 'test.retry_reason'
61
+ const TEST_HAS_FAILED_ALL_RETRIES = 'test.has_failed_all_retries'
61
62
 
62
63
  const CI_APP_ORIGIN = 'ciapp-test'
63
64
 
@@ -103,6 +104,12 @@ const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g')
103
104
  const DD_CAPABILITIES_TEST_IMPACT_ANALYSIS = '_dd.library_capabilities.test_impact_analysis'
104
105
  const DD_CAPABILITIES_EARLY_FLAKE_DETECTION = '_dd.library_capabilities.early_flake_detection'
105
106
  const DD_CAPABILITIES_AUTO_TEST_RETRIES = '_dd.library_capabilities.auto_test_retries'
107
+ const DD_CAPABILITIES_TEST_MANAGEMENT_QUARANTINE = '_dd.library_capabilities.test_management.quarantine'
108
+ const DD_CAPABILITIES_TEST_MANAGEMENT_DISABLE = '_dd.library_capabilities.test_management.disable'
109
+ const DD_CAPABILITIES_TEST_MANAGEMENT_ATTEMPT_TO_FIX = '_dd.library_capabilities.test_management.attempt_to_fix'
110
+ const UNSUPPORTED_TIA_FRAMEWORKS = ['playwright', 'vitest']
111
+ const UNSUPPORTED_TIA_FRAMEWORKS_PARALLEL_MODE = ['cucumber', 'mocha']
112
+ const UNSUPPORTED_ATTEMPT_TO_FIX_FRAMEWORKS_PARALLEL_MODE = ['mocha']
106
113
 
107
114
  const TEST_LEVEL_EVENT_TYPES = [
108
115
  'test',
@@ -120,9 +127,16 @@ const DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX = 'snapshot_id'
120
127
  const DI_DEBUG_ERROR_FILE_SUFFIX = 'file'
121
128
  const DI_DEBUG_ERROR_LINE_SUFFIX = 'line'
122
129
 
130
+ // Test Management tags
131
+ const TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX = 'test.test_management.is_attempt_to_fix'
123
132
  const TEST_MANAGEMENT_IS_DISABLED = 'test.test_management.is_test_disabled'
124
133
  const TEST_MANAGEMENT_IS_QUARANTINED = 'test.test_management.is_quarantined'
125
134
  const TEST_MANAGEMENT_ENABLED = 'test.test_management.enabled'
135
+ const TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED = 'test.test_management.attempt_to_fix_passed'
136
+
137
+ // Test Management utils strings
138
+ const ATTEMPT_TO_FIX_STRING = "Retried by Datadog's Test Management"
139
+ const ATTEMPT_TEST_NAME_REGEX = new RegExp(ATTEMPT_TO_FIX_STRING + ' \\(#\\d+\\): ', 'g')
126
140
 
127
141
  module.exports = {
128
142
  TEST_CODE_OWNERS,
@@ -155,6 +169,7 @@ module.exports = {
155
169
  TEST_EARLY_FLAKE_ENABLED,
156
170
  TEST_EARLY_FLAKE_ABORT_REASON,
157
171
  TEST_RETRY_REASON,
172
+ TEST_HAS_FAILED_ALL_RETRIES,
158
173
  getTestEnvironmentMetadata,
159
174
  getTestParametersString,
160
175
  finishAllTraceSpans,
@@ -192,7 +207,9 @@ module.exports = {
192
207
  EFD_STRING,
193
208
  EFD_TEST_NAME_REGEX,
194
209
  removeEfdStringFromTestName,
210
+ removeAttemptToFixStringFromTestName,
195
211
  addEfdStringToTestName,
212
+ addAttemptToFixStringToTestName,
196
213
  getIsFaultyEarlyFlakeDetection,
197
214
  TEST_BROWSER_DRIVER,
198
215
  TEST_BROWSER_DRIVER_VERSION,
@@ -202,6 +219,9 @@ module.exports = {
202
219
  DD_CAPABILITIES_TEST_IMPACT_ANALYSIS,
203
220
  DD_CAPABILITIES_EARLY_FLAKE_DETECTION,
204
221
  DD_CAPABILITIES_AUTO_TEST_RETRIES,
222
+ DD_CAPABILITIES_TEST_MANAGEMENT_QUARANTINE,
223
+ DD_CAPABILITIES_TEST_MANAGEMENT_DISABLE,
224
+ DD_CAPABILITIES_TEST_MANAGEMENT_ATTEMPT_TO_FIX,
205
225
  TEST_LEVEL_EVENT_TYPES,
206
226
  getNumFromKnownTests,
207
227
  getFileAndLineNumberFromError,
@@ -212,9 +232,12 @@ module.exports = {
212
232
  DI_DEBUG_ERROR_LINE_SUFFIX,
213
233
  getFormattedError,
214
234
  DD_TEST_IS_USER_PROVIDED_SERVICE,
235
+ TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX,
215
236
  TEST_MANAGEMENT_IS_DISABLED,
216
237
  TEST_MANAGEMENT_IS_QUARANTINED,
217
- TEST_MANAGEMENT_ENABLED
238
+ TEST_MANAGEMENT_ENABLED,
239
+ TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED,
240
+ getLibraryCapabilitiesTags
218
241
  }
219
242
 
220
243
  // Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
@@ -622,10 +645,18 @@ function addEfdStringToTestName (testName, numAttempt) {
622
645
  return `${EFD_STRING} (#${numAttempt}): ${testName}`
623
646
  }
624
647
 
648
+ function addAttemptToFixStringToTestName (testName, numAttempt) {
649
+ return `${ATTEMPT_TO_FIX_STRING} (#${numAttempt}): ${testName}`
650
+ }
651
+
625
652
  function removeEfdStringFromTestName (testName) {
626
653
  return testName.replace(EFD_TEST_NAME_REGEX, '')
627
654
  }
628
655
 
656
+ function removeAttemptToFixStringFromTestName (testName) {
657
+ return testName.replace(ATTEMPT_TEST_NAME_REGEX, '')
658
+ }
659
+
629
660
  function getIsFaultyEarlyFlakeDetection (projectSuites, testsBySuiteName, faultyThresholdPercentage) {
630
661
  let newSuites = 0
631
662
  for (const suite of projectSuites) {
@@ -718,3 +749,33 @@ function getFormattedError (error, repositoryRoot) {
718
749
 
719
750
  return newError
720
751
  }
752
+
753
+ function getLibraryCapabilitiesTags (testFramework, isParallel) {
754
+ function isTiaSupported (testFramework, isParallel) {
755
+ if (UNSUPPORTED_TIA_FRAMEWORKS.includes(testFramework)) {
756
+ return false
757
+ }
758
+ if (isParallel && UNSUPPORTED_TIA_FRAMEWORKS_PARALLEL_MODE.includes(testFramework)) {
759
+ return false
760
+ }
761
+ return true
762
+ }
763
+
764
+ function isAttemptToFixSupported (testFramework, isParallel) {
765
+ if (isParallel && UNSUPPORTED_ATTEMPT_TO_FIX_FRAMEWORKS_PARALLEL_MODE.includes(testFramework)) {
766
+ return false
767
+ }
768
+ return true
769
+ }
770
+
771
+ return {
772
+ [DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: isTiaSupported(testFramework, isParallel) ? '1' : undefined,
773
+ [DD_CAPABILITIES_EARLY_FLAKE_DETECTION]: '1',
774
+ [DD_CAPABILITIES_AUTO_TEST_RETRIES]: '1',
775
+ [DD_CAPABILITIES_TEST_MANAGEMENT_QUARANTINE]: '1',
776
+ [DD_CAPABILITIES_TEST_MANAGEMENT_DISABLE]: '1',
777
+ [DD_CAPABILITIES_TEST_MANAGEMENT_ATTEMPT_TO_FIX]: isAttemptToFixSupported(testFramework, isParallel)
778
+ ? '2'
779
+ : undefined
780
+ }
781
+ }