dd-trace 5.85.0 → 5.86.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/index.d.ts +20 -1
- package/package.json +1 -1
- package/packages/datadog-core/src/storage.js +30 -12
- package/packages/datadog-instrumentations/src/jest.js +34 -9
- package/packages/datadog-instrumentations/src/mocha/main.js +9 -0
- package/packages/datadog-instrumentations/src/prisma.js +225 -30
- package/packages/datadog-instrumentations/src/ws.js +22 -0
- package/packages/datadog-plugin-cucumber/src/index.js +4 -10
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +5 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -4
- package/packages/datadog-plugin-http/src/server.js +23 -8
- package/packages/datadog-plugin-jest/src/index.js +29 -10
- package/packages/datadog-plugin-jest/src/util.js +7 -1
- package/packages/datadog-plugin-mocha/src/index.js +5 -17
- package/packages/datadog-plugin-playwright/src/index.js +3 -0
- package/packages/datadog-plugin-prisma/src/datadog-tracing-helper.js +37 -14
- package/packages/datadog-plugin-prisma/src/index.js +8 -5
- package/packages/datadog-plugin-router/src/index.js +28 -19
- package/packages/datadog-plugin-vitest/src/index.js +6 -10
- package/packages/datadog-plugin-ws/src/server.js +8 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +1 -0
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +19 -0
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +15 -0
- package/packages/dd-trace/src/ci-visibility/telemetry.js +36 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +44 -1
- package/packages/dd-trace/src/exporters/common/request.js +35 -35
- package/packages/dd-trace/src/id.js +1 -1
- package/packages/dd-trace/src/lambda/context.js +27 -0
- package/packages/dd-trace/src/lambda/handler.js +5 -18
- package/packages/dd-trace/src/log/writer.js +1 -5
- package/packages/dd-trace/src/plugins/ci_plugin.js +63 -1
- package/packages/dd-trace/src/plugins/util/git.js +27 -30
- package/packages/dd-trace/src/plugins/util/test.js +3 -1
- package/packages/dd-trace/src/plugins/util/web.js +1 -0
- package/packages/dd-trace/src/profiling/config.js +6 -14
- package/packages/dd-trace/src/profiling/exporters/agent.js +23 -24
- package/packages/dd-trace/src/profiling/profiler.js +2 -0
- package/packages/dd-trace/src/startup-log.js +1 -0
package/index.d.ts
CHANGED
|
@@ -295,7 +295,18 @@ interface Plugins {
|
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
declare namespace tracer {
|
|
298
|
-
export type SpanOptions = opentracing.SpanOptions
|
|
298
|
+
export type SpanOptions = Omit<opentracing.SpanOptions, 'childOf'> & {
|
|
299
|
+
/**
|
|
300
|
+
* Set childOf to 'null' to create a root span without a parent, even when a parent span
|
|
301
|
+
* exists in the current async context. If 'undefined' the parent will be inferred from the
|
|
302
|
+
* existing async context.
|
|
303
|
+
*/
|
|
304
|
+
childOf?: opentracing.Span | opentracing.SpanContext | null;
|
|
305
|
+
/**
|
|
306
|
+
* Optional name of the integration that crated this span.
|
|
307
|
+
*/
|
|
308
|
+
integrationName?: string;
|
|
309
|
+
};
|
|
299
310
|
export { Tracer };
|
|
300
311
|
|
|
301
312
|
export interface TraceOptions extends Analyzable {
|
|
@@ -749,6 +760,14 @@ declare namespace tracer {
|
|
|
749
760
|
*/
|
|
750
761
|
dbmPropagationMode?: 'disabled' | 'service' | 'full'
|
|
751
762
|
|
|
763
|
+
/**
|
|
764
|
+
* Whether to enable Data Streams Monitoring.
|
|
765
|
+
* Can also be enabled via the DD_DATA_STREAMS_ENABLED environment variable.
|
|
766
|
+
* When not provided, the value of DD_DATA_STREAMS_ENABLED is used.
|
|
767
|
+
* @default false
|
|
768
|
+
*/
|
|
769
|
+
dsmEnabled?: boolean
|
|
770
|
+
|
|
752
771
|
/**
|
|
753
772
|
* Configuration of the AppSec protection. Can be a boolean as an alias to `appsec.enabled`.
|
|
754
773
|
*/
|
package/package.json
CHANGED
|
@@ -59,28 +59,46 @@ class DatadogStorage extends AsyncLocalStorage {
|
|
|
59
59
|
return stores.get(handle)
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// To handle all versions always correct, feature detect AsyncContextFrame and
|
|
65
|
+
// fallback to manual approach if not active. With ACF `run` delegates to
|
|
66
|
+
// `enterWith`, without ACF `run` does not.
|
|
67
|
+
const isACFActive = (() => {
|
|
68
|
+
let active = false
|
|
69
|
+
const als = new AsyncLocalStorage()
|
|
70
|
+
als.enterWith = () => { active = true }
|
|
71
|
+
als.run(1, () => {})
|
|
72
|
+
als.disable()
|
|
73
|
+
return active
|
|
74
|
+
})()
|
|
75
|
+
|
|
76
|
+
if (!isACFActive) {
|
|
77
|
+
const superGetStore = AsyncLocalStorage.prototype.getStore
|
|
78
|
+
const superEnterWith = AsyncLocalStorage.prototype.enterWith
|
|
62
79
|
|
|
63
80
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
81
|
+
* Override the `run` method to manually call `enterWith` and `getStore`
|
|
82
|
+
* when not using AsyncContextFrame.
|
|
83
|
+
*
|
|
84
|
+
* Without ACF, super.run() won't call this.enterWith(), so the WeakMap handle
|
|
85
|
+
* is never created and getStore() would fail.
|
|
86
|
+
*
|
|
69
87
|
* @template R
|
|
70
|
-
* @template
|
|
88
|
+
* @template {unknown[]} TArgs
|
|
71
89
|
* @param {Store<unknown>} store
|
|
72
|
-
* @param {() => R} fn
|
|
73
|
-
* @param {
|
|
90
|
+
* @param {(...args: TArgs) => R} fn
|
|
91
|
+
* @param {TArgs} args
|
|
74
92
|
* @returns {R}
|
|
75
93
|
* @override
|
|
76
94
|
*/
|
|
77
|
-
run (store, fn, ...args) {
|
|
78
|
-
const prior =
|
|
95
|
+
DatadogStorage.prototype.run = function run (store, fn, ...args) {
|
|
96
|
+
const prior = superGetStore.call(this)
|
|
79
97
|
this.enterWith(store)
|
|
80
98
|
try {
|
|
81
99
|
return Reflect.apply(fn, null, args)
|
|
82
100
|
} finally {
|
|
83
|
-
|
|
101
|
+
superEnterWith.call(this, prior)
|
|
84
102
|
}
|
|
85
103
|
}
|
|
86
104
|
}
|
|
@@ -111,4 +129,4 @@ function storage (namespace) {
|
|
|
111
129
|
return storages[namespace]
|
|
112
130
|
}
|
|
113
131
|
|
|
114
|
-
module.exports = { storage }
|
|
132
|
+
module.exports = { storage, isACFActive }
|
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
getCoveredFilenamesFromCoverage,
|
|
8
8
|
JEST_WORKER_TRACE_PAYLOAD_CODE,
|
|
9
9
|
JEST_WORKER_COVERAGE_PAYLOAD_CODE,
|
|
10
|
+
JEST_WORKER_TELEMETRY_PAYLOAD_CODE,
|
|
10
11
|
getTestLineStart,
|
|
11
12
|
getTestSuitePath,
|
|
12
13
|
getTestParametersString,
|
|
@@ -16,6 +17,7 @@ const {
|
|
|
16
17
|
isModifiedTest,
|
|
17
18
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
18
19
|
const {
|
|
20
|
+
SEED_SUFFIX_RE,
|
|
19
21
|
getFormattedJestTestParameters,
|
|
20
22
|
getJestTestName,
|
|
21
23
|
getJestSuitesToRun,
|
|
@@ -35,6 +37,7 @@ const testSuiteErrorCh = channel('ci:jest:test-suite:error')
|
|
|
35
37
|
const workerReportTraceCh = channel('ci:jest:worker-report:trace')
|
|
36
38
|
const workerReportCoverageCh = channel('ci:jest:worker-report:coverage')
|
|
37
39
|
const workerReportLogsCh = channel('ci:jest:worker-report:logs')
|
|
40
|
+
const workerReportTelemetryCh = channel('ci:jest:worker-report:telemetry')
|
|
38
41
|
|
|
39
42
|
const testSuiteCodeCoverageCh = channel('ci:jest:test-suite:code-coverage')
|
|
40
43
|
|
|
@@ -55,6 +58,7 @@ const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
|
|
|
55
58
|
// Message sent by jest's main process to workers to run a test suite (=test file)
|
|
56
59
|
// https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-worker/src/types.ts#L37
|
|
57
60
|
const CHILD_MESSAGE_CALL = 1
|
|
61
|
+
|
|
58
62
|
// Maximum time we'll wait for the tracer to flush
|
|
59
63
|
const FLUSH_TIMEOUT = 10_000
|
|
60
64
|
|
|
@@ -515,6 +519,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
515
519
|
}
|
|
516
520
|
}
|
|
517
521
|
if (event.name === 'test_done') {
|
|
522
|
+
const originalError = event.test?.errors?.[0]
|
|
518
523
|
let status = 'pass'
|
|
519
524
|
if (event.test.errors && event.test.errors.length) {
|
|
520
525
|
status = 'fail'
|
|
@@ -594,7 +599,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
594
599
|
const shouldSetProbe = this.isDiEnabled && willBeRetriedByFailedTestReplay && numTestExecutions === 1
|
|
595
600
|
testErrCh.publish({
|
|
596
601
|
...ctx.currentStore,
|
|
597
|
-
error: formatJestError(
|
|
602
|
+
error: formatJestError(originalError),
|
|
598
603
|
shouldSetProbe,
|
|
599
604
|
promises,
|
|
600
605
|
finalStatus,
|
|
@@ -954,7 +959,7 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
954
959
|
isTestManagementTestsEnabled = false
|
|
955
960
|
testManagementTests = {}
|
|
956
961
|
} else {
|
|
957
|
-
testManagementTests = receivedTestManagementTests
|
|
962
|
+
testManagementTests = receivedTestManagementTests || {}
|
|
958
963
|
}
|
|
959
964
|
} catch (err) {
|
|
960
965
|
log.error('Jest test management tests error', err)
|
|
@@ -1012,33 +1017,39 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
1012
1017
|
* The rationale behind is the following: you may still be able to block your CI pipeline by gating
|
|
1013
1018
|
* on flakiness (the test will be considered flaky), but you may choose to unblock the pipeline too.
|
|
1014
1019
|
*/
|
|
1020
|
+
let numEfdFailedTestsToIgnore = 0
|
|
1015
1021
|
if (isEarlyFlakeDetectionEnabled) {
|
|
1016
|
-
let numFailedTestsToIgnore = 0
|
|
1017
1022
|
for (const testStatuses of newTestsTestStatuses.values()) {
|
|
1018
1023
|
const { pass, fail } = getTestStats(testStatuses)
|
|
1019
1024
|
if (pass > 0) { // as long as one passes, we'll consider the test passed
|
|
1020
|
-
|
|
1025
|
+
numEfdFailedTestsToIgnore += fail
|
|
1021
1026
|
}
|
|
1022
1027
|
}
|
|
1023
1028
|
// If every test that failed was an EFD retry, we'll consider the suite passed
|
|
1024
|
-
if (
|
|
1029
|
+
if (numEfdFailedTestsToIgnore !== 0 && result.results.numFailedTests === numEfdFailedTestsToIgnore) {
|
|
1025
1030
|
result.results.success = true
|
|
1026
1031
|
}
|
|
1027
1032
|
}
|
|
1028
1033
|
|
|
1034
|
+
let numFailedQuarantinedTests = 0
|
|
1035
|
+
let numFailedQuarantinedOrDisabledAttemptedToFixTests = 0
|
|
1029
1036
|
if (isTestManagementTestsEnabled) {
|
|
1030
1037
|
const failedTests = result
|
|
1031
1038
|
.results
|
|
1032
1039
|
.testResults.flatMap(({ testResults, testFilePath: testSuiteAbsolutePath }) => (
|
|
1033
1040
|
testResults.map(({ fullName: testName, status }) => (
|
|
1034
|
-
{
|
|
1041
|
+
{
|
|
1042
|
+
// Strip @fast-check/jest seed suffix so the name matches what was reported via TEST_NAME
|
|
1043
|
+
testName: testSuiteAbsolutePathsWithFastCheck.has(testSuiteAbsolutePath)
|
|
1044
|
+
? testName.replace(SEED_SUFFIX_RE, '')
|
|
1045
|
+
: testName,
|
|
1046
|
+
testSuiteAbsolutePath,
|
|
1047
|
+
status,
|
|
1048
|
+
}
|
|
1035
1049
|
))
|
|
1036
1050
|
))
|
|
1037
1051
|
.filter(({ status }) => status === 'failed')
|
|
1038
1052
|
|
|
1039
|
-
let numFailedQuarantinedTests = 0
|
|
1040
|
-
let numFailedQuarantinedOrDisabledAttemptedToFixTests = 0
|
|
1041
|
-
|
|
1042
1053
|
for (const { testName, testSuiteAbsolutePath } of failedTests) {
|
|
1043
1054
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, result.globalConfig.rootDir)
|
|
1044
1055
|
const testManagementTest = testManagementTests
|
|
@@ -1069,6 +1080,16 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
1069
1080
|
}
|
|
1070
1081
|
}
|
|
1071
1082
|
|
|
1083
|
+
// Combined check: if all failed tests are accounted for by EFD (flaky retries) and/or quarantine,
|
|
1084
|
+
// we should consider the suite passed even when neither check alone covers all failures.
|
|
1085
|
+
if (!result.results.success && (isEarlyFlakeDetectionEnabled || isTestManagementTestsEnabled)) {
|
|
1086
|
+
const totalIgnoredFailures =
|
|
1087
|
+
numEfdFailedTestsToIgnore + numFailedQuarantinedTests + numFailedQuarantinedOrDisabledAttemptedToFixTests
|
|
1088
|
+
if (totalIgnoredFailures !== 0 && result.results.numFailedTests === totalIgnoredFailures) {
|
|
1089
|
+
result.results.success = true
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1072
1093
|
// Determine session status after EFD and quarantine checks have potentially modified success
|
|
1073
1094
|
let status, error
|
|
1074
1095
|
if (result.results.success) {
|
|
@@ -1534,6 +1555,10 @@ function onMessageWrapper (onMessage) {
|
|
|
1534
1555
|
workerReportLogsCh.publish(data)
|
|
1535
1556
|
return
|
|
1536
1557
|
}
|
|
1558
|
+
if (code === JEST_WORKER_TELEMETRY_PAYLOAD_CODE) { // datadog telemetry payload
|
|
1559
|
+
workerReportTelemetryCh.publish(data)
|
|
1560
|
+
return
|
|
1561
|
+
}
|
|
1537
1562
|
return onMessage.apply(this, arguments)
|
|
1538
1563
|
}
|
|
1539
1564
|
}
|
|
@@ -165,6 +165,15 @@ function getOnEndHandler (isParallel) {
|
|
|
165
165
|
this.failures -= numFailedQuarantinedTests + numFailedRetriedQuarantinedOrDisabledTests
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
// Recompute status after EFD and quarantine adjustments have reduced failure counts
|
|
169
|
+
if (status === 'fail') {
|
|
170
|
+
if (this.stats) {
|
|
171
|
+
status = this.stats.failures === 0 ? 'pass' : 'fail'
|
|
172
|
+
} else {
|
|
173
|
+
status = this.failures === 0 ? 'pass' : 'fail'
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
168
177
|
if (status === 'fail') {
|
|
169
178
|
error = new Error(`Failed tests: ${this.failures}.`)
|
|
170
179
|
}
|
|
@@ -4,45 +4,186 @@ const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
|
|
|
4
4
|
const { channel, addHook } = require('./helpers/instrument')
|
|
5
5
|
const prismaHelperInit = channel('apm:prisma:helper:init')
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {object} EnvValue
|
|
9
|
+
* @property {string|undefined} [value]
|
|
10
|
+
* @property {string|null|undefined} [fromEnvVar]
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {object} DatasourceConfig
|
|
15
|
+
* @property {string|EnvValue|undefined} [url]
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {object} PrismaRuntimeConfig
|
|
20
|
+
* @property {Record<string, DatasourceConfig>|undefined} [inlineDatasources]
|
|
21
|
+
* @property {Record<string, { url?: string }>|undefined} [overrideDatasources]
|
|
22
|
+
* @property {Record<string, { url?: string }>|undefined} [datasources]
|
|
23
|
+
* @property {string[]|undefined} [datasourceNames]
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} DbConfig
|
|
28
|
+
* @property {string|undefined} [user]
|
|
29
|
+
* @property {string|undefined} [password]
|
|
30
|
+
* @property {string|undefined} [host]
|
|
31
|
+
* @property {string|number|undefined} [port]
|
|
32
|
+
* @property {string|undefined} [database]
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {object} AdapterConfig
|
|
37
|
+
* @property {string|undefined} [connectionString]
|
|
38
|
+
* @property {string|undefined} [user]
|
|
39
|
+
* @property {string|undefined} [password]
|
|
40
|
+
* @property {string|undefined} [host]
|
|
41
|
+
* @property {string|number|undefined} [port]
|
|
42
|
+
* @property {string|undefined} [database]
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {object} Adapter
|
|
47
|
+
* @property {AdapterConfig|undefined} [config]
|
|
48
|
+
* @property {{ options?: AdapterConfig }|undefined} [externalPool]
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @typedef {object} PrismaClientConfig
|
|
53
|
+
* @property {string|undefined} [datasourceUrl]
|
|
54
|
+
* @property {Record<string, { url?: string }>|undefined} [datasources]
|
|
55
|
+
* @property {Adapter|undefined} [adapter]
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @typedef {object} PrismaHelperCtx
|
|
60
|
+
* @property {DbConfig} [dbConfig]
|
|
61
|
+
* @property {import('../../datadog-plugin-prisma/src/datadog-tracing-helper')} [helper]
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {string|EnvValue|undefined} envValue
|
|
66
|
+
* @returns {string|undefined}
|
|
67
|
+
*/
|
|
68
|
+
function resolveEnvValue (envValue) {
|
|
69
|
+
return typeof envValue === 'object' && envValue
|
|
70
|
+
? (envValue.value || getEnvironmentVariable(envValue.fromEnvVar ?? ''))
|
|
71
|
+
: envValue
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {PrismaRuntimeConfig|undefined} config
|
|
76
|
+
* @param {string} datasourceName
|
|
77
|
+
* @returns {string|undefined}
|
|
78
|
+
*/
|
|
79
|
+
function resolveDatasourceUrl (config, datasourceName) {
|
|
80
|
+
return resolveEnvValue(config?.inlineDatasources?.[datasourceName]?.url) ??
|
|
81
|
+
config?.overrideDatasources?.[datasourceName]?.url ??
|
|
82
|
+
config?.datasources?.[datasourceName]?.url ??
|
|
83
|
+
getEnvironmentVariable('DATABASE_URL')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {DbConfig} dbConfig
|
|
88
|
+
* @returns {DbConfig|undefined}
|
|
89
|
+
*/
|
|
90
|
+
function normalizeDbConfig (dbConfig) {
|
|
91
|
+
dbConfig.port = dbConfig.port == null ? undefined : String(dbConfig.port)
|
|
92
|
+
const hasValues = dbConfig.user || dbConfig.password || dbConfig.host || dbConfig.port || dbConfig.database
|
|
93
|
+
return hasValues ? dbConfig : undefined
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {Adapter|undefined} adapter
|
|
98
|
+
* @returns {DbConfig|undefined}
|
|
99
|
+
*/
|
|
100
|
+
function resolveAdapterDbConfig (adapter) {
|
|
101
|
+
const adapterConfig = adapter?.config || adapter?.externalPool?.options
|
|
102
|
+
if (!adapterConfig) {
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (typeof adapterConfig === 'string') {
|
|
107
|
+
return parseDBString(adapterConfig)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const parsed = parseDBString(adapterConfig.connectionString)
|
|
111
|
+
if (parsed) {
|
|
112
|
+
return parsed
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return normalizeDbConfig({
|
|
116
|
+
user: adapterConfig.user,
|
|
117
|
+
password: adapterConfig.password,
|
|
118
|
+
host: adapterConfig.host,
|
|
119
|
+
port: adapterConfig.port,
|
|
120
|
+
database: adapterConfig.database,
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {PrismaClientConfig|undefined} clientConfig
|
|
126
|
+
* @param {string} datasourceName
|
|
127
|
+
* @param {DbConfig|undefined} runtimeDbConfig
|
|
128
|
+
* @returns {DbConfig|undefined}
|
|
129
|
+
*/
|
|
130
|
+
function resolveClientDbConfig (clientConfig, datasourceName, runtimeDbConfig) {
|
|
131
|
+
return resolveAdapterDbConfig(clientConfig?.adapter) ||
|
|
132
|
+
parseDBString(clientConfig?.datasources?.[datasourceName]?.url ?? clientConfig?.datasourceUrl) ||
|
|
133
|
+
runtimeDbConfig
|
|
134
|
+
}
|
|
9
135
|
|
|
10
|
-
|
|
136
|
+
/**
|
|
137
|
+
* @param {unknown} runtime
|
|
138
|
+
* @param {string} versions
|
|
139
|
+
* @param {string} [name]
|
|
140
|
+
* @param {boolean} [isIitm]
|
|
141
|
+
* @returns {object}
|
|
142
|
+
*/
|
|
143
|
+
const prismaHook = (runtime, versions, name, isIitm) => {
|
|
144
|
+
/**
|
|
145
|
+
* @typedef {{ getPrismaClient?: (config: PrismaRuntimeConfig, ...args: unknown[]) => Function }} PrismaRuntime
|
|
146
|
+
*/
|
|
147
|
+
const prismaRuntime = /** @type {PrismaRuntime} */ (runtime)
|
|
148
|
+
const originalGetPrismaClient = prismaRuntime.getPrismaClient
|
|
11
149
|
|
|
12
|
-
|
|
150
|
+
if (!originalGetPrismaClient) {
|
|
151
|
+
return runtime
|
|
152
|
+
}
|
|
13
153
|
|
|
154
|
+
/**
|
|
155
|
+
* @param {PrismaRuntimeConfig|undefined} config
|
|
156
|
+
*/
|
|
14
157
|
const wrappedGetPrismaClient = function (config) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
config?.inlineDatasources?.db?.url?.value ??
|
|
19
|
-
config?.inlineDatasources?.db?.url ??
|
|
20
|
-
config?.overrideDatasources?.db?.url ??
|
|
21
|
-
config?.datasources?.db?.url ??
|
|
22
|
-
config?.datasourceUrl ??
|
|
23
|
-
getEnvironmentVariable('DATABASE_URL')
|
|
24
|
-
|
|
25
|
-
if (datasourceUrl && !prismaHelperCtx.dbConfig) {
|
|
26
|
-
prismaHelperCtx.dbConfig = parseDBString(datasourceUrl)
|
|
27
|
-
}
|
|
28
|
-
prismaHelperInit.publish(prismaHelperCtx)
|
|
158
|
+
const datasourceName = config?.datasourceNames?.[0] || 'db'
|
|
159
|
+
const runtimeDatasourceUrl = resolveDatasourceUrl(config, datasourceName)
|
|
160
|
+
const runtimeDbConfig = parseDBString(runtimeDatasourceUrl)
|
|
29
161
|
|
|
30
162
|
const PrismaClient = originalGetPrismaClient.call(this, config)
|
|
31
163
|
return class WrappedPrismaClientClass extends PrismaClient {
|
|
32
164
|
constructor (clientConfig) {
|
|
33
165
|
super(clientConfig)
|
|
34
|
-
|
|
35
|
-
|
|
166
|
+
/**
|
|
167
|
+
* @type {PrismaHelperCtx}
|
|
168
|
+
*/
|
|
169
|
+
const prismaHelperCtx = {
|
|
170
|
+
dbConfig: resolveClientDbConfig(clientConfig, datasourceName, runtimeDbConfig),
|
|
171
|
+
}
|
|
172
|
+
prismaHelperInit.publish(prismaHelperCtx)
|
|
173
|
+
|
|
174
|
+
const helper = prismaHelperCtx.helper
|
|
175
|
+
this._tracingHelper = helper
|
|
176
|
+
this._engine.tracingHelper = helper
|
|
36
177
|
}
|
|
37
178
|
}
|
|
38
179
|
}
|
|
39
180
|
|
|
40
181
|
if (isIitm) {
|
|
41
|
-
|
|
182
|
+
prismaRuntime.getPrismaClient = wrappedGetPrismaClient
|
|
42
183
|
return runtime
|
|
43
184
|
}
|
|
44
185
|
|
|
45
|
-
return new Proxy(
|
|
186
|
+
return new Proxy(prismaRuntime, {
|
|
46
187
|
get (target, prop) {
|
|
47
188
|
if (prop === 'getPrismaClient') {
|
|
48
189
|
return wrappedGetPrismaClient
|
|
@@ -62,14 +203,68 @@ for (const config of prismaConfigs) {
|
|
|
62
203
|
addHook(config, prismaHook)
|
|
63
204
|
}
|
|
64
205
|
|
|
206
|
+
/**
|
|
207
|
+
* @param {string|undefined} dbString
|
|
208
|
+
* @returns {DbConfig|undefined}
|
|
209
|
+
*/
|
|
65
210
|
function parseDBString (dbString) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
211
|
+
if (!dbString || typeof dbString !== 'string') {
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const sqlServerConfig = parseSqlServerConnectionString(dbString)
|
|
216
|
+
if (sqlServerConfig) {
|
|
217
|
+
return sqlServerConfig
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const url = new URL(dbString)
|
|
222
|
+
return normalizeDbConfig({
|
|
223
|
+
user: url.username,
|
|
224
|
+
password: url.password,
|
|
225
|
+
host: url.hostname,
|
|
226
|
+
port: url.port,
|
|
227
|
+
database: url.pathname?.slice(1) || undefined,
|
|
228
|
+
})
|
|
229
|
+
} catch {}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @param {string} dbString
|
|
234
|
+
* @returns {DbConfig|undefined}
|
|
235
|
+
*/
|
|
236
|
+
function parseSqlServerConnectionString (dbString) {
|
|
237
|
+
if (!dbString.startsWith('sqlserver://')) {
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
const segments = dbString.slice(12).split(';').filter(Boolean)
|
|
241
|
+
if (!segments.length) {
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let hostPart = segments.shift()
|
|
246
|
+
let user
|
|
247
|
+
let password
|
|
248
|
+
if (hostPart?.includes('@')) {
|
|
249
|
+
const [userInfo, hostInfo] = hostPart.split('@')
|
|
250
|
+
hostPart = hostInfo
|
|
251
|
+
;[user, password] = userInfo.split(':')
|
|
73
252
|
}
|
|
74
|
-
|
|
253
|
+
|
|
254
|
+
let database
|
|
255
|
+
for (const segment of segments) {
|
|
256
|
+
const [rawKey, ...rawValue] = segment.split('=')
|
|
257
|
+
const value = rawValue.join('=').trim()
|
|
258
|
+
const key = rawKey?.trim().toLowerCase()
|
|
259
|
+
if (!key || !value) {
|
|
260
|
+
continue
|
|
261
|
+
}
|
|
262
|
+
if (key === 'database' || key === 'databasename' || key === 'db') database = value
|
|
263
|
+
else if (key === 'user' || key === 'username' || key === 'uid') user = value
|
|
264
|
+
else if (key === 'password' || key === 'pwd') password = value
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const [host, port] = hostPart?.split(':') ?? []
|
|
268
|
+
|
|
269
|
+
return normalizeDbConfig({ user, password, host, port, database })
|
|
75
270
|
}
|
|
@@ -13,6 +13,7 @@ const producerCh = tracingChannel('ws:send')
|
|
|
13
13
|
const receiverCh = tracingChannel('ws:receive')
|
|
14
14
|
const closeCh = tracingChannel('ws:close')
|
|
15
15
|
const emitCh = channel('tracing:ws:server:connect:emit')
|
|
16
|
+
const setSocketCh = channel('tracing:ws:server:connect:setSocket')
|
|
16
17
|
// TODO: Add a error channel / handle error events properly.
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -181,12 +182,33 @@ addHook({
|
|
|
181
182
|
return ws
|
|
182
183
|
})
|
|
183
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Prevent internal event handlers (data, close, etc.) registered by the ws library to
|
|
187
|
+
* capture the connection span in their async context. Otherwise, the
|
|
188
|
+
* finished connection span is retained for the entire lifetime of the WebSocket
|
|
189
|
+
* (via ACF -> handle -> WeakMap).
|
|
190
|
+
*
|
|
191
|
+
* @param {Function} setSocket
|
|
192
|
+
* @returns {(...args: unknown[]) => unknown}
|
|
193
|
+
*/
|
|
194
|
+
function wrapSetSocket (setSocket) {
|
|
195
|
+
return function wrappedSetSocket (...args) {
|
|
196
|
+
if (!setSocketCh.hasSubscribers) {
|
|
197
|
+
return setSocket.apply(this, args)
|
|
198
|
+
}
|
|
199
|
+
return setSocketCh.runStores({}, () => {
|
|
200
|
+
return setSocket.apply(this, args)
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
184
205
|
addHook({
|
|
185
206
|
name: 'ws',
|
|
186
207
|
file: 'lib/websocket.js',
|
|
187
208
|
versions: ['>=8.0.0'],
|
|
188
209
|
}, moduleExports => {
|
|
189
210
|
const ws = /** @type {WebSocketClass} */ (moduleExports)
|
|
211
|
+
shimmer.wrap(ws.prototype, 'setSocket', wrapSetSocket)
|
|
190
212
|
shimmer.wrap(ws.prototype, 'send', wrapSend)
|
|
191
213
|
shimmer.wrap(ws.prototype, 'close', wrapClose)
|
|
192
214
|
|
|
@@ -13,7 +13,6 @@ const {
|
|
|
13
13
|
isModifiedTest,
|
|
14
14
|
CUCUMBER_IS_PARALLEL,
|
|
15
15
|
ITR_CORRELATION_ID,
|
|
16
|
-
TEST_BROWSER_DRIVER,
|
|
17
16
|
TEST_CODE_OWNERS,
|
|
18
17
|
TEST_EARLY_FLAKE_ABORT_REASON,
|
|
19
18
|
TEST_EARLY_FLAKE_ENABLED,
|
|
@@ -21,7 +20,6 @@ const {
|
|
|
21
20
|
TEST_IS_MODIFIED,
|
|
22
21
|
TEST_IS_NEW,
|
|
23
22
|
TEST_IS_RETRY,
|
|
24
|
-
TEST_IS_RUM_ACTIVE,
|
|
25
23
|
TEST_ITR_FORCED_RUN,
|
|
26
24
|
TEST_ITR_UNSKIPPABLE,
|
|
27
25
|
TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED,
|
|
@@ -108,7 +106,9 @@ class CucumberPlugin extends CiPlugin {
|
|
|
108
106
|
this.testModuleSpan.finish()
|
|
109
107
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
110
108
|
this.testSessionSpan.finish()
|
|
111
|
-
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session'
|
|
109
|
+
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session', {
|
|
110
|
+
hasFailedTestReplay: this.libraryConfig?.isDiEnabled || undefined,
|
|
111
|
+
})
|
|
112
112
|
finishAllTraceSpans(this.testSessionSpan)
|
|
113
113
|
this.telemetry.count(TELEMETRY_TEST_SESSION, {
|
|
114
114
|
provider: this.ciProviderName,
|
|
@@ -362,13 +362,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
362
362
|
}
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
-
const
|
|
366
|
-
const telemetryTags = {
|
|
367
|
-
hasCodeOwners: !!spanTags[TEST_CODE_OWNERS],
|
|
368
|
-
isNew,
|
|
369
|
-
isRum: spanTags[TEST_IS_RUM_ACTIVE] === 'true',
|
|
370
|
-
browserDriver: spanTags[TEST_BROWSER_DRIVER],
|
|
371
|
-
}
|
|
365
|
+
const telemetryTags = this.getTestTelemetryTags(span)
|
|
372
366
|
span.finish()
|
|
373
367
|
if (!isStep) {
|
|
374
368
|
this.telemetry.ciVisEvent(
|
|
@@ -976,11 +976,15 @@ class CypressPlugin {
|
|
|
976
976
|
this.finishedTestsByFile[testSuite] = [finishedTest]
|
|
977
977
|
}
|
|
978
978
|
// test spans are finished at after:spec
|
|
979
|
+
const activeSpanTags = this.activeTestSpan.context()._tags
|
|
979
980
|
this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
|
|
980
|
-
hasCodeOwners: !!
|
|
981
|
+
hasCodeOwners: !!activeSpanTags[TEST_CODE_OWNERS],
|
|
981
982
|
isNew,
|
|
982
983
|
isRum: isRUMActive,
|
|
983
984
|
browserDriver: 'cypress',
|
|
985
|
+
isQuarantined: isQuarantinedFromSupport,
|
|
986
|
+
isModified,
|
|
987
|
+
isDisabled: activeSpanTags[TEST_MANAGEMENT_IS_DISABLED] === 'true',
|
|
984
988
|
})
|
|
985
989
|
this.activeTestSpan = null
|
|
986
990
|
|
|
@@ -127,9 +127,10 @@ class GoogleCloudPubsubConsumerPlugin extends ConsumerPlugin {
|
|
|
127
127
|
const meta = {
|
|
128
128
|
'gcloud.project_id': subscription.pubsub.projectId,
|
|
129
129
|
'pubsub.topic': topic,
|
|
130
|
+
'pubsub.message_id': message.messageId,
|
|
130
131
|
'span.kind': 'consumer',
|
|
132
|
+
'pubsub.subscription': subscription.name,
|
|
131
133
|
'pubsub.subscription_type': 'pull',
|
|
132
|
-
'pubsub.span_type': 'message_processing',
|
|
133
134
|
'messaging.operation': 'receive',
|
|
134
135
|
base_service: baseService,
|
|
135
136
|
service_override_type: 'custom',
|
|
@@ -171,9 +172,6 @@ class GoogleCloudPubsubConsumerPlugin extends ConsumerPlugin {
|
|
|
171
172
|
metrics,
|
|
172
173
|
}, ctx)
|
|
173
174
|
|
|
174
|
-
if (message.id) {
|
|
175
|
-
span.setTag('pubsub.message_id', message.id)
|
|
176
|
-
}
|
|
177
175
|
if (message.publishTime) {
|
|
178
176
|
span.setTag('pubsub.publish_time', message.publishTime.toISOString())
|
|
179
177
|
}
|