dd-trace 5.41.1 → 5.42.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.
- package/LICENSE-3rdparty.csv +1 -0
- package/index.d.ts +8 -1
- package/package.json +6 -3
- package/packages/datadog-esbuild/index.js +3 -1
- package/packages/datadog-instrumentations/src/cucumber.js +37 -29
- package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +102 -0
- package/packages/datadog-instrumentations/src/{check_require_cache.js → helpers/check-require-cache.js} +2 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/register.js +4 -1
- package/packages/datadog-instrumentations/src/jest.js +72 -49
- package/packages/datadog-instrumentations/src/langchain.js +29 -10
- package/packages/datadog-instrumentations/src/mocha/main.js +53 -34
- package/packages/datadog-instrumentations/src/mocha/utils.js +34 -24
- package/packages/datadog-instrumentations/src/mocha/worker.js +7 -8
- package/packages/datadog-instrumentations/src/openai.js +1 -1
- package/packages/datadog-instrumentations/src/playwright.js +37 -30
- package/packages/datadog-instrumentations/src/vitest.js +64 -29
- package/packages/datadog-plugin-cucumber/src/index.js +13 -4
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +41 -35
- package/packages/datadog-plugin-cypress/src/plugin.js +10 -0
- package/packages/datadog-plugin-google-cloud-vertexai/src/index.js +195 -0
- package/packages/datadog-plugin-jest/src/index.js +18 -6
- package/packages/datadog-plugin-langchain/src/handlers/embedding.js +4 -1
- package/packages/datadog-plugin-mocha/src/index.js +13 -4
- package/packages/datadog-plugin-playwright/src/index.js +19 -5
- package/packages/datadog-plugin-vitest/src/index.js +41 -17
- package/packages/dd-trace/src/appsec/api_security_sampler.js +7 -3
- package/packages/dd-trace/src/appsec/blocking.js +23 -16
- package/packages/dd-trace/src/appsec/graphql.js +13 -6
- package/packages/dd-trace/src/appsec/rasp/utils.js +0 -1
- package/packages/dd-trace/src/appsec/reporter.js +35 -0
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -3
- package/packages/dd-trace/src/appsec/telemetry/index.js +5 -1
- package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -1
- package/packages/dd-trace/src/appsec/telemetry/waf.js +16 -1
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +43 -13
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +15 -14
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -2
- package/packages/dd-trace/src/ci-visibility/telemetry.js +2 -1
- package/packages/dd-trace/src/ci-visibility/{quarantined-tests/get-quarantined-tests.js → test-management/get-test-management-tests.js} +5 -5
- package/packages/dd-trace/src/config.js +11 -1
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -2
- package/packages/dd-trace/src/lambda/runtime/patch.js +5 -3
- package/packages/dd-trace/src/lambda/runtime/ritm.js +13 -18
- package/packages/dd-trace/src/llmobs/plugins/openai.js +27 -2
- package/packages/dd-trace/src/opentracing/span.js +3 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +38 -10
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/git.js +7 -3
- package/packages/dd-trace/src/plugins/util/test.js +10 -0
- package/packages/dd-trace/src/plugins/util/web.js +5 -2
- package/packages/dd-trace/src/priority_sampler.js +116 -15
- package/packages/dd-trace/src/sampler.js +9 -0
- package/packages/dd-trace/src/standalone/product.js +6 -2
- package/packages/dd-trace/src/startup-log.js +2 -1
- package/packages/dd-trace/src/telemetry/metrics.js +0 -8
- package/packages/dd-trace/src/tracer.js +1 -1
- /package/packages/datadog-instrumentations/src/{utils/src → helpers}/extract-package-and-module-path.js +0 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { MEASURED } = require('../../../ext/tags')
|
|
4
|
+
const { storage } = require('../../datadog-core')
|
|
5
|
+
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
6
|
+
const makeUtilities = require('../../dd-trace/src/plugins/util/llm')
|
|
7
|
+
|
|
8
|
+
class GoogleCloudVertexAIPlugin extends TracingPlugin {
|
|
9
|
+
static get id () { return 'google-cloud-vertexai' }
|
|
10
|
+
static get prefix () {
|
|
11
|
+
return 'tracing:apm:vertexai:request'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
constructor () {
|
|
15
|
+
super(...arguments)
|
|
16
|
+
|
|
17
|
+
Object.assign(this, makeUtilities('vertexai', this._tracerConfig))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
bindStart (ctx) {
|
|
21
|
+
const { instance, request, resource, stream } = ctx
|
|
22
|
+
|
|
23
|
+
const tags = this.tagRequest(request, instance, stream)
|
|
24
|
+
|
|
25
|
+
const span = this.startSpan('vertexai.request', {
|
|
26
|
+
service: this.config.service,
|
|
27
|
+
resource,
|
|
28
|
+
kind: 'client',
|
|
29
|
+
meta: {
|
|
30
|
+
[MEASURED]: 1,
|
|
31
|
+
...tags
|
|
32
|
+
}
|
|
33
|
+
}, false)
|
|
34
|
+
|
|
35
|
+
const store = storage('legacy').getStore() || {}
|
|
36
|
+
ctx.currentStore = { ...store, span }
|
|
37
|
+
|
|
38
|
+
return ctx.currentStore
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
asyncEnd (ctx) {
|
|
42
|
+
const span = ctx.currentStore?.span
|
|
43
|
+
if (!span) return
|
|
44
|
+
|
|
45
|
+
const { result } = ctx
|
|
46
|
+
|
|
47
|
+
const response = result?.response
|
|
48
|
+
if (response) {
|
|
49
|
+
const tags = this.tagResponse(response)
|
|
50
|
+
span.addTags(tags)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
span.finish()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
tagRequest (request, instance, stream) {
|
|
57
|
+
const model = extractModel(instance)
|
|
58
|
+
const tags = {
|
|
59
|
+
'vertexai.request.model': model
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const history = instance.historyInternal
|
|
63
|
+
let contents = typeof request === 'string' || Array.isArray(request) ? request : request.contents
|
|
64
|
+
if (history) {
|
|
65
|
+
contents = [...history, ...(Array.isArray(contents) ? contents : [contents])]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const generationConfig = instance.generationConfig || {}
|
|
69
|
+
for (const key of Object.keys(generationConfig)) {
|
|
70
|
+
const transformedKey = key.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase()
|
|
71
|
+
tags[`vertexai.request.generation_config.${transformedKey}`] = JSON.stringify(generationConfig[key])
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (stream) {
|
|
75
|
+
tags['vertexai.request.stream'] = true
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!this.isPromptCompletionSampled()) return tags
|
|
79
|
+
|
|
80
|
+
const systemInstructions = extractSystemInstructions(instance)
|
|
81
|
+
|
|
82
|
+
for (const [idx, systemInstruction] of systemInstructions.entries()) {
|
|
83
|
+
tags[`vertexai.request.system_instruction.${idx}.text`] = systemInstruction
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (typeof contents === 'string') {
|
|
87
|
+
tags['vertexai.request.contents.0.text'] = contents
|
|
88
|
+
return tags
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const [contentIdx, content] of contents.entries()) {
|
|
92
|
+
this.tagRequestContent(tags, content, contentIdx)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return tags
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
tagRequestPart (part, tags, partIdx, contentIdx) {
|
|
99
|
+
tags[`vertexai.request.contents.${contentIdx}.parts.${partIdx}.text`] = this.normalize(part.text)
|
|
100
|
+
|
|
101
|
+
const functionCall = part.functionCall
|
|
102
|
+
const functionResponse = part.functionResponse
|
|
103
|
+
|
|
104
|
+
if (functionCall) {
|
|
105
|
+
tags[`vertexai.request.contents.${contentIdx}.parts.${partIdx}.function_call.name`] = functionCall.name
|
|
106
|
+
tags[`vertexai.request.contents.${contentIdx}.parts.${partIdx}.function_call.args`] =
|
|
107
|
+
this.normalize(JSON.stringify(functionCall.args))
|
|
108
|
+
}
|
|
109
|
+
if (functionResponse) {
|
|
110
|
+
tags[`vertexai.request.contents.${contentIdx}.parts.${partIdx}.function_response.name`] =
|
|
111
|
+
functionResponse.name
|
|
112
|
+
tags[`vertexai.request.contents.${contentIdx}.parts.${partIdx}.function_response.response`] =
|
|
113
|
+
this.normalize(JSON.stringify(functionResponse.response))
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
tagRequestContent (tags, content, contentIdx) {
|
|
118
|
+
if (typeof content === 'string') {
|
|
119
|
+
tags[`vertexai.request.contents.${contentIdx}.text`] = this.normalize(content)
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (content.text || content.functionCall || content.functionResponse) {
|
|
124
|
+
this.tagRequestPart(content, tags, 0, contentIdx)
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const { role, parts } = content
|
|
129
|
+
if (role) {
|
|
130
|
+
tags[`vertexai.request.contents.${contentIdx}.role`] = role
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const [partIdx, part] of parts.entries()) {
|
|
134
|
+
this.tagRequestPart(part, tags, partIdx, contentIdx)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
tagResponse (response) {
|
|
139
|
+
const tags = {}
|
|
140
|
+
|
|
141
|
+
const candidates = response.candidates
|
|
142
|
+
for (const [candidateIdx, candidate] of candidates.entries()) {
|
|
143
|
+
const finishReason = candidate.finishReason
|
|
144
|
+
if (finishReason) {
|
|
145
|
+
tags[`vertexai.response.candidates.${candidateIdx}.finish_reason`] = finishReason
|
|
146
|
+
}
|
|
147
|
+
const candidateContent = candidate.content
|
|
148
|
+
const role = candidateContent.role
|
|
149
|
+
tags[`vertexai.response.candidates.${candidateIdx}.content.role`] = role
|
|
150
|
+
|
|
151
|
+
if (!this.isPromptCompletionSampled()) continue
|
|
152
|
+
|
|
153
|
+
const parts = candidateContent.parts
|
|
154
|
+
for (const [partIdx, part] of parts.entries()) {
|
|
155
|
+
const text = part.text
|
|
156
|
+
tags[`vertexai.response.candidates.${candidateIdx}.content.parts.${partIdx}.text`] =
|
|
157
|
+
this.normalize(String(text))
|
|
158
|
+
|
|
159
|
+
const functionCall = part.functionCall
|
|
160
|
+
if (!functionCall) continue
|
|
161
|
+
|
|
162
|
+
tags[`vertexai.response.candidates.${candidateIdx}.content.parts.${partIdx}.function_call.name`] =
|
|
163
|
+
functionCall.name
|
|
164
|
+
tags[`vertexai.response.candidates.${candidateIdx}.content.parts.${partIdx}.function_call.args`] =
|
|
165
|
+
this.normalize(JSON.stringify(functionCall.args))
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const tokenCounts = response.usageMetadata
|
|
170
|
+
if (tokenCounts) {
|
|
171
|
+
tags['vertexai.response.usage.prompt_tokens'] = tokenCounts.promptTokenCount
|
|
172
|
+
tags['vertexai.response.usage.completion_tokens'] = tokenCounts.candidatesTokenCount
|
|
173
|
+
tags['vertexai.response.usage.total_tokens'] = tokenCounts.totalTokenCount
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return tags
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function extractModel (instance) {
|
|
181
|
+
const model = instance.model || instance.resourcePath || instance.publisherModelEndpoint
|
|
182
|
+
return model?.split('/').pop()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function extractSystemInstructions (instance) {
|
|
186
|
+
// systemInstruction is either a string or a Content object
|
|
187
|
+
// Content objects have parts (Part[]) and a role
|
|
188
|
+
const systemInstruction = instance.systemInstruction
|
|
189
|
+
if (!systemInstruction) return []
|
|
190
|
+
if (typeof systemInstruction === 'string') return [systemInstruction]
|
|
191
|
+
|
|
192
|
+
return systemInstruction.parts?.map(part => part.text)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = GoogleCloudVertexAIPlugin
|
|
@@ -26,7 +26,8 @@ const {
|
|
|
26
26
|
getFormattedError,
|
|
27
27
|
TEST_RETRY_REASON,
|
|
28
28
|
TEST_MANAGEMENT_ENABLED,
|
|
29
|
-
TEST_MANAGEMENT_IS_QUARANTINED
|
|
29
|
+
TEST_MANAGEMENT_IS_QUARANTINED,
|
|
30
|
+
TEST_MANAGEMENT_IS_DISABLED
|
|
30
31
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
31
32
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
32
33
|
const id = require('../../dd-trace/src/id')
|
|
@@ -108,7 +109,7 @@ class JestPlugin extends CiPlugin {
|
|
|
108
109
|
error,
|
|
109
110
|
isEarlyFlakeDetectionEnabled,
|
|
110
111
|
isEarlyFlakeDetectionFaulty,
|
|
111
|
-
|
|
112
|
+
isTestManagementTestsEnabled,
|
|
112
113
|
onDone
|
|
113
114
|
}) => {
|
|
114
115
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
@@ -140,7 +141,7 @@ class JestPlugin extends CiPlugin {
|
|
|
140
141
|
if (isEarlyFlakeDetectionFaulty) {
|
|
141
142
|
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
|
|
142
143
|
}
|
|
143
|
-
if (
|
|
144
|
+
if (isTestManagementTestsEnabled) {
|
|
144
145
|
this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
|
|
145
146
|
}
|
|
146
147
|
|
|
@@ -150,7 +151,10 @@ class JestPlugin extends CiPlugin {
|
|
|
150
151
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
|
|
151
152
|
finishAllTraceSpans(this.testSessionSpan)
|
|
152
153
|
|
|
153
|
-
this.telemetry.count(TELEMETRY_TEST_SESSION, {
|
|
154
|
+
this.telemetry.count(TELEMETRY_TEST_SESSION, {
|
|
155
|
+
provider: this.ciProviderName,
|
|
156
|
+
autoInjected: !!process.env.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER
|
|
157
|
+
})
|
|
154
158
|
|
|
155
159
|
this.tracer._exporter.flush(() => {
|
|
156
160
|
if (onDone) {
|
|
@@ -172,7 +176,7 @@ class JestPlugin extends CiPlugin {
|
|
|
172
176
|
config._ddEarlyFlakeDetectionNumRetries = this.libraryConfig?.earlyFlakeDetectionNumRetries ?? 0
|
|
173
177
|
config._ddRepositoryRoot = this.repositoryRoot
|
|
174
178
|
config._ddIsFlakyTestRetriesEnabled = this.libraryConfig?.isFlakyTestRetriesEnabled ?? false
|
|
175
|
-
config.
|
|
179
|
+
config._ddIsTestManagementTestsEnabled = this.libraryConfig?.isTestManagementEnabled ?? false
|
|
176
180
|
config._ddFlakyTestRetriesCount = this.libraryConfig?.flakyTestRetriesCount
|
|
177
181
|
config._ddIsDiEnabled = this.libraryConfig?.isDiEnabled ?? false
|
|
178
182
|
config._ddIsKnownTestsEnabled = this.libraryConfig?.isKnownTestsEnabled ?? false
|
|
@@ -377,9 +381,17 @@ class JestPlugin extends CiPlugin {
|
|
|
377
381
|
}
|
|
378
382
|
})
|
|
379
383
|
|
|
380
|
-
this.addSub('ci:jest:test:skip', (
|
|
384
|
+
this.addSub('ci:jest:test:skip', ({
|
|
385
|
+
test,
|
|
386
|
+
isDisabled
|
|
387
|
+
}) => {
|
|
381
388
|
const span = this.startTestSpan(test)
|
|
382
389
|
span.setTag(TEST_STATUS, 'skip')
|
|
390
|
+
|
|
391
|
+
if (isDisabled) {
|
|
392
|
+
span.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
|
|
393
|
+
}
|
|
394
|
+
|
|
383
395
|
span.finish()
|
|
384
396
|
})
|
|
385
397
|
}
|
|
@@ -43,7 +43,10 @@ class LangChainEmbeddingHandler extends LangChainHandler {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
extractApiKey (instance) {
|
|
46
|
-
const apiKey =
|
|
46
|
+
const apiKey =
|
|
47
|
+
instance.clientConfig?.apiKey ||
|
|
48
|
+
instance.apiKey ||
|
|
49
|
+
instance.client?.apiKey
|
|
47
50
|
if (!apiKey || apiKey.length < 4) return ''
|
|
48
51
|
return `...${apiKey.slice(-4)}`
|
|
49
52
|
}
|
|
@@ -33,7 +33,8 @@ const {
|
|
|
33
33
|
TEST_BROWSER_DRIVER,
|
|
34
34
|
TEST_RETRY_REASON,
|
|
35
35
|
TEST_MANAGEMENT_ENABLED,
|
|
36
|
-
TEST_MANAGEMENT_IS_QUARANTINED
|
|
36
|
+
TEST_MANAGEMENT_IS_QUARANTINED,
|
|
37
|
+
TEST_MANAGEMENT_IS_DISABLED
|
|
37
38
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
38
39
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
39
40
|
const {
|
|
@@ -311,7 +312,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
311
312
|
error,
|
|
312
313
|
isEarlyFlakeDetectionEnabled,
|
|
313
314
|
isEarlyFlakeDetectionFaulty,
|
|
314
|
-
|
|
315
|
+
isTestManagementEnabled,
|
|
315
316
|
isParallel
|
|
316
317
|
}) => {
|
|
317
318
|
if (this.testSessionSpan) {
|
|
@@ -328,7 +329,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
328
329
|
this.testSessionSpan.setTag(MOCHA_IS_PARALLEL, 'true')
|
|
329
330
|
}
|
|
330
331
|
|
|
331
|
-
if (
|
|
332
|
+
if (isTestManagementEnabled) {
|
|
332
333
|
this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
|
|
333
334
|
}
|
|
334
335
|
|
|
@@ -359,7 +360,10 @@ class MochaPlugin extends CiPlugin {
|
|
|
359
360
|
this.testSessionSpan.finish()
|
|
360
361
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
|
|
361
362
|
finishAllTraceSpans(this.testSessionSpan)
|
|
362
|
-
this.telemetry.count(TELEMETRY_TEST_SESSION, {
|
|
363
|
+
this.telemetry.count(TELEMETRY_TEST_SESSION, {
|
|
364
|
+
provider: this.ciProviderName,
|
|
365
|
+
autoInjected: !!process.env.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER
|
|
366
|
+
})
|
|
363
367
|
}
|
|
364
368
|
this.libraryConfig = null
|
|
365
369
|
this.tracer._exporter.flush()
|
|
@@ -405,6 +409,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
405
409
|
isEfdRetry,
|
|
406
410
|
testStartLine,
|
|
407
411
|
isParallel,
|
|
412
|
+
isDisabled,
|
|
408
413
|
isQuarantined
|
|
409
414
|
} = testInfo
|
|
410
415
|
|
|
@@ -424,6 +429,10 @@ class MochaPlugin extends CiPlugin {
|
|
|
424
429
|
extraTags[MOCHA_IS_PARALLEL] = 'true'
|
|
425
430
|
}
|
|
426
431
|
|
|
432
|
+
if (isDisabled) {
|
|
433
|
+
extraTags[TEST_MANAGEMENT_IS_DISABLED] = 'true'
|
|
434
|
+
}
|
|
435
|
+
|
|
427
436
|
if (isQuarantined) {
|
|
428
437
|
extraTags[TEST_MANAGEMENT_IS_QUARANTINED] = 'true'
|
|
429
438
|
}
|
|
@@ -19,7 +19,8 @@ const {
|
|
|
19
19
|
TEST_RETRY_REASON,
|
|
20
20
|
TEST_MANAGEMENT_IS_QUARANTINED,
|
|
21
21
|
TEST_MANAGEMENT_ENABLED,
|
|
22
|
-
TEST_BROWSER_NAME
|
|
22
|
+
TEST_BROWSER_NAME,
|
|
23
|
+
TEST_MANAGEMENT_IS_DISABLED
|
|
23
24
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
24
25
|
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
25
26
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
@@ -44,7 +45,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
44
45
|
this.addSub('ci:playwright:session:finish', ({
|
|
45
46
|
status,
|
|
46
47
|
isEarlyFlakeDetectionEnabled,
|
|
47
|
-
|
|
48
|
+
isTestManagementTestsEnabled,
|
|
48
49
|
onDone
|
|
49
50
|
}) => {
|
|
50
51
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
@@ -64,7 +65,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
64
65
|
this.testSessionSpan.setTag('error', error)
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
if (
|
|
68
|
+
if (isTestManagementTestsEnabled) {
|
|
68
69
|
this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
|
|
69
70
|
}
|
|
70
71
|
|
|
@@ -73,7 +74,10 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
73
74
|
this.testSessionSpan.finish()
|
|
74
75
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
|
|
75
76
|
finishAllTraceSpans(this.testSessionSpan)
|
|
76
|
-
this.telemetry.count(TELEMETRY_TEST_SESSION, {
|
|
77
|
+
this.telemetry.count(TELEMETRY_TEST_SESSION, {
|
|
78
|
+
provider: this.ciProviderName,
|
|
79
|
+
autoInjected: !!process.env.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER
|
|
80
|
+
})
|
|
77
81
|
appClosingTelemetry()
|
|
78
82
|
this.tracer._exporter.flush(onDone)
|
|
79
83
|
this.numFailedTests = 0
|
|
@@ -132,12 +136,22 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
132
136
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
|
|
133
137
|
})
|
|
134
138
|
|
|
135
|
-
this.addSub('ci:playwright:test:start', ({
|
|
139
|
+
this.addSub('ci:playwright:test:start', ({
|
|
140
|
+
testName,
|
|
141
|
+
testSuiteAbsolutePath,
|
|
142
|
+
testSourceLine,
|
|
143
|
+
browserName,
|
|
144
|
+
isDisabled
|
|
145
|
+
}) => {
|
|
136
146
|
const store = storage('legacy').getStore()
|
|
137
147
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
|
|
138
148
|
const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
139
149
|
const span = this.startTestSpan(testName, testSuite, testSourceFile, testSourceLine, browserName)
|
|
140
150
|
|
|
151
|
+
if (isDisabled) {
|
|
152
|
+
span.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
|
|
153
|
+
}
|
|
154
|
+
|
|
141
155
|
this.enter(span, store)
|
|
142
156
|
})
|
|
143
157
|
this.addSub('ci:playwright:test:finish', ({
|
|
@@ -20,7 +20,10 @@ const {
|
|
|
20
20
|
TEST_EARLY_FLAKE_ABORT_REASON,
|
|
21
21
|
TEST_RETRY_REASON,
|
|
22
22
|
TEST_MANAGEMENT_ENABLED,
|
|
23
|
-
TEST_MANAGEMENT_IS_QUARANTINED
|
|
23
|
+
TEST_MANAGEMENT_IS_QUARANTINED,
|
|
24
|
+
TEST_MANAGEMENT_IS_DISABLED,
|
|
25
|
+
DD_CAPABILITIES_EARLY_FLAKE_DETECTION,
|
|
26
|
+
DD_CAPABILITIES_AUTO_TEST_RETRIES
|
|
24
27
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
25
28
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
26
29
|
const {
|
|
@@ -50,16 +53,16 @@ class VitestPlugin extends CiPlugin {
|
|
|
50
53
|
onDone(!testsForThisTestSuite.includes(testName))
|
|
51
54
|
})
|
|
52
55
|
|
|
53
|
-
this.addSub('ci:vitest:test:is-
|
|
56
|
+
this.addSub('ci:vitest:test:is-disabled', ({ testManagementTests, testSuiteAbsolutePath, testName, onDone }) => {
|
|
54
57
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
const { isDisabled } = this.getTestProperties(testManagementTests, testSuite, testName)
|
|
59
|
+
|
|
60
|
+
onDone(isDisabled ?? false)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
this.addSub('ci:vitest:test:is-quarantined', ({ testManagementTests, testSuiteAbsolutePath, testName, onDone }) => {
|
|
64
|
+
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
65
|
+
const { isQuarantined } = this.getTestProperties(testManagementTests, testSuite, testName)
|
|
63
66
|
|
|
64
67
|
onDone(isQuarantined ?? false)
|
|
65
68
|
})
|
|
@@ -178,7 +181,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
178
181
|
}
|
|
179
182
|
})
|
|
180
183
|
|
|
181
|
-
this.addSub('ci:vitest:test:skip', ({ testName, testSuiteAbsolutePath, isNew }) => {
|
|
184
|
+
this.addSub('ci:vitest:test:skip', ({ testName, testSuiteAbsolutePath, isNew, isDisabled }) => {
|
|
182
185
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
183
186
|
const testSpan = this.startTestSpan(
|
|
184
187
|
testName,
|
|
@@ -188,6 +191,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
188
191
|
[TEST_SOURCE_FILE]: testSuite,
|
|
189
192
|
[TEST_SOURCE_START]: 1, // we can't get the proper start line in vitest
|
|
190
193
|
[TEST_STATUS]: 'skip',
|
|
194
|
+
...(isDisabled ? { [TEST_MANAGEMENT_IS_DISABLED]: 'true' } : {}),
|
|
191
195
|
...(isNew ? { [TEST_IS_NEW]: 'true' } : {})
|
|
192
196
|
}
|
|
193
197
|
)
|
|
@@ -197,7 +201,12 @@ class VitestPlugin extends CiPlugin {
|
|
|
197
201
|
testSpan.finish()
|
|
198
202
|
})
|
|
199
203
|
|
|
200
|
-
this.addSub('ci:vitest:test-suite:start', ({
|
|
204
|
+
this.addSub('ci:vitest:test-suite:start', ({
|
|
205
|
+
testSuiteAbsolutePath,
|
|
206
|
+
frameworkVersion,
|
|
207
|
+
isFlakyTestRetriesEnabled,
|
|
208
|
+
isEarlyFlakeDetectionEnabled
|
|
209
|
+
}) => {
|
|
201
210
|
this.command = process.env.DD_CIVISIBILITY_TEST_COMMAND
|
|
202
211
|
this.frameworkVersion = frameworkVersion
|
|
203
212
|
const testSessionSpanContext = this.tracer.extract('text_map', {
|
|
@@ -213,8 +222,13 @@ class VitestPlugin extends CiPlugin {
|
|
|
213
222
|
[TEST_SESSION_NAME]: testSessionName
|
|
214
223
|
}
|
|
215
224
|
}
|
|
216
|
-
if (this.tracer._exporter.
|
|
217
|
-
|
|
225
|
+
if (this.tracer._exporter.addMetadataTags) {
|
|
226
|
+
metadataTags.test = {
|
|
227
|
+
...metadataTags.test,
|
|
228
|
+
[DD_CAPABILITIES_EARLY_FLAKE_DETECTION]: isEarlyFlakeDetectionEnabled ? 'true' : 'false',
|
|
229
|
+
[DD_CAPABILITIES_AUTO_TEST_RETRIES]: isFlakyTestRetriesEnabled ? 'true' : 'false'
|
|
230
|
+
}
|
|
231
|
+
this.tracer._exporter.addMetadataTags(metadataTags)
|
|
218
232
|
}
|
|
219
233
|
|
|
220
234
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
|
|
@@ -277,7 +291,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
277
291
|
testCodeCoverageLinesTotal,
|
|
278
292
|
isEarlyFlakeDetectionEnabled,
|
|
279
293
|
isEarlyFlakeDetectionFaulty,
|
|
280
|
-
|
|
294
|
+
isTestManagementTestsEnabled,
|
|
281
295
|
onFinish
|
|
282
296
|
}) => {
|
|
283
297
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
@@ -296,7 +310,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
296
310
|
if (isEarlyFlakeDetectionFaulty) {
|
|
297
311
|
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
|
|
298
312
|
}
|
|
299
|
-
if (
|
|
313
|
+
if (isTestManagementTestsEnabled) {
|
|
300
314
|
this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
|
|
301
315
|
}
|
|
302
316
|
this.testModuleSpan.finish()
|
|
@@ -304,10 +318,20 @@ class VitestPlugin extends CiPlugin {
|
|
|
304
318
|
this.testSessionSpan.finish()
|
|
305
319
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
|
|
306
320
|
finishAllTraceSpans(this.testSessionSpan)
|
|
307
|
-
this.telemetry.count(TELEMETRY_TEST_SESSION, {
|
|
321
|
+
this.telemetry.count(TELEMETRY_TEST_SESSION, {
|
|
322
|
+
provider: this.ciProviderName,
|
|
323
|
+
autoInjected: !!process.env.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER
|
|
324
|
+
})
|
|
308
325
|
this.tracer._exporter.flush(onFinish)
|
|
309
326
|
})
|
|
310
327
|
}
|
|
328
|
+
|
|
329
|
+
getTestProperties (testManagementTests, testSuite, testName) {
|
|
330
|
+
const { disabled: isDisabled, quarantined: isQuarantined } =
|
|
331
|
+
testManagementTests?.vitest?.suites?.[testSuite]?.tests?.[testName]?.properties || {}
|
|
332
|
+
|
|
333
|
+
return { isDisabled, isQuarantined }
|
|
334
|
+
}
|
|
311
335
|
}
|
|
312
336
|
|
|
313
337
|
module.exports = VitestPlugin
|
|
@@ -8,12 +8,16 @@ const { AUTO_REJECT, USER_REJECT } = require('../../../../ext/priority')
|
|
|
8
8
|
const MAX_SIZE = 4096
|
|
9
9
|
|
|
10
10
|
let enabled
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @type {TTLCache}
|
|
14
|
+
*/
|
|
11
15
|
let sampledRequests
|
|
12
16
|
|
|
13
17
|
class NoopTTLCache {
|
|
14
18
|
clear () { }
|
|
15
|
-
set (
|
|
16
|
-
has (
|
|
19
|
+
set (_key, _value) { return undefined }
|
|
20
|
+
has (_key) { return false }
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
function configure ({ apiSecurity }) {
|
|
@@ -48,7 +52,7 @@ function sampleRequest (req, res, force = false) {
|
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
if (force) {
|
|
51
|
-
sampledRequests.set(key)
|
|
55
|
+
sampledRequests.set(key, undefined)
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
return true
|
|
@@ -100,29 +100,36 @@ function getBlockingData (req, specificType, actionParameters) {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
function block (req, res, rootSpan, abortController, actionParameters = defaultBlockingActionParameters) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
103
|
+
try {
|
|
104
|
+
if (res.headersSent) {
|
|
105
|
+
log.warn('[ASM] Cannot send blocking response when headers have already been sent')
|
|
107
106
|
|
|
108
|
-
|
|
107
|
+
throw new Error('Headers have already been sent')
|
|
108
|
+
}
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
'appsec.blocked': 'true'
|
|
112
|
-
})
|
|
110
|
+
const { body, headers, statusCode } = getBlockingData(req, null, actionParameters)
|
|
113
111
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
for (const headerName of res.getHeaderNames()) {
|
|
113
|
+
res.removeHeader(headerName)
|
|
114
|
+
}
|
|
117
115
|
|
|
118
|
-
|
|
116
|
+
res.writeHead(statusCode, headers)
|
|
119
117
|
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
// this is needed to call the original end method, since express-session replaces it
|
|
119
|
+
res.constructor.prototype.end.call(res, body)
|
|
122
120
|
|
|
123
|
-
|
|
121
|
+
rootSpan.setTag('appsec.blocked', 'true')
|
|
124
122
|
|
|
125
|
-
|
|
123
|
+
responseBlockedSet.add(res)
|
|
124
|
+
abortController?.abort()
|
|
125
|
+
|
|
126
|
+
return true
|
|
127
|
+
} catch (err) {
|
|
128
|
+
rootSpan?.setTag('_dd.appsec.block.failed', 1)
|
|
129
|
+
log.error('[ASM] Blocking error', err)
|
|
130
|
+
|
|
131
|
+
return false
|
|
132
|
+
}
|
|
126
133
|
}
|
|
127
134
|
|
|
128
135
|
function getBlockingAction (actions) {
|
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
getBlockingData,
|
|
8
8
|
getBlockingAction
|
|
9
9
|
} = require('./blocking')
|
|
10
|
+
const log = require('../log')
|
|
10
11
|
const waf = require('./waf')
|
|
11
12
|
const addresses = require('./addresses')
|
|
12
13
|
const web = require('../plugins/util/web')
|
|
@@ -94,14 +95,20 @@ function beforeWriteApolloGraphqlResponse ({ abortController, abortData }) {
|
|
|
94
95
|
const rootSpan = web.root(req)
|
|
95
96
|
if (!rootSpan) return
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
try {
|
|
99
|
+
const blockingData = getBlockingData(req, specificBlockingTypes.GRAPHQL, requestData.wafAction)
|
|
100
|
+
abortData.statusCode = blockingData.statusCode
|
|
101
|
+
abortData.headers = blockingData.headers
|
|
102
|
+
abortData.message = blockingData.body
|
|
101
103
|
|
|
102
|
-
|
|
104
|
+
rootSpan.setTag('appsec.blocked', 'true')
|
|
103
105
|
|
|
104
|
-
|
|
106
|
+
abortController?.abort()
|
|
107
|
+
} catch (err) {
|
|
108
|
+
rootSpan.setTag('_dd.appsec.block.failed', 1)
|
|
109
|
+
|
|
110
|
+
log.error('[ASM] Blocking error', err)
|
|
111
|
+
}
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
graphqlRequestData.delete(req)
|
|
@@ -49,7 +49,6 @@ function handleResult (actions, req, res, abortController, config) {
|
|
|
49
49
|
|
|
50
50
|
const blockingAction = getBlockingAction(actions)
|
|
51
51
|
if (blockingAction) {
|
|
52
|
-
const rootSpan = web.root(req)
|
|
53
52
|
// Should block only in express
|
|
54
53
|
if (rootSpan?.context()._name === 'express.request') {
|
|
55
54
|
const abortError = new DatadogRaspAbortError(req, res, blockingAction)
|