dd-trace 6.11.0 → 6.13.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 +5 -0
- package/ci/test-optimization-validation/framework-adapters/cypress.js +9 -11
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
- package/index.d.ts +143 -25
- package/package.json +26 -25
- package/packages/datadog-instrumentations/src/bunyan.js +4 -2
- package/packages/datadog-instrumentations/src/cucumber.js +4 -4
- package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +28 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/jest.js +140 -18
- package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
- package/packages/datadog-instrumentations/src/mariadb.js +7 -0
- package/packages/datadog-instrumentations/src/mocha/utils.js +5 -2
- package/packages/datadog-instrumentations/src/pino.js +15 -4
- package/packages/datadog-instrumentations/src/playwright-reporter.js +149 -1
- package/packages/datadog-instrumentations/src/playwright.js +67 -53
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +13 -12
- package/packages/datadog-instrumentations/src/vitest-worker.js +11 -0
- package/packages/datadog-instrumentations/src/webdriverio.js +26 -0
- package/packages/datadog-instrumentations/src/winston.js +6 -3
- package/packages/datadog-plugin-cypress/src/support.js +1 -0
- package/packages/datadog-plugin-http/src/client.js +2 -15
- package/packages/datadog-plugin-http2/src/client.js +2 -15
- package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
- package/packages/datadog-plugin-playwright/src/index.js +13 -1
- package/packages/datadog-plugin-undici/src/index.js +2 -15
- package/packages/dd-trace/src/aiguard/evaluation.js +94 -20
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +21 -9
- package/packages/dd-trace/src/aiguard/noop.js +9 -1
- package/packages/dd-trace/src/aiguard/redaction.js +183 -0
- package/packages/dd-trace/src/aiguard/sdk.js +7 -2
- package/packages/dd-trace/src/aiguard/tags.js +2 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
- package/packages/dd-trace/src/appsec/reporter.js +2 -0
- package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
- package/packages/dd-trace/src/appsec/telemetry/index.js +13 -0
- package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -0
- package/packages/dd-trace/src/appsec/telemetry/waf.js +24 -0
- package/packages/dd-trace/src/carrier.js +107 -48
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +5 -3
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +51 -26
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -11
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
- package/packages/dd-trace/src/ci-visibility/final-flush.js +20 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +5 -3
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +196 -29
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +4 -2
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +4 -2
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
- package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -0
- package/packages/dd-trace/src/config/index.js +2 -1
- package/packages/dd-trace/src/config/supported-configurations.json +27 -0
- package/packages/dd-trace/src/debugger/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -10
- package/packages/dd-trace/src/debugger/devtools_client/send.js +4 -3
- package/packages/dd-trace/src/debugger/index.js +4 -2
- package/packages/dd-trace/src/debugger/inspect-segment.js +154 -0
- package/packages/dd-trace/src/dogstatsd.js +45 -18
- package/packages/dd-trace/src/encode/agentless-json.js +5 -4
- package/packages/dd-trace/src/evp_proxy/constants.js +8 -0
- package/packages/dd-trace/src/evp_proxy/direct.js +64 -0
- package/packages/dd-trace/src/evp_proxy/discovery.js +133 -0
- package/packages/dd-trace/src/evp_proxy/path.js +22 -0
- package/packages/dd-trace/src/exporters/agent/index.js +22 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +4 -4
- package/packages/dd-trace/src/exporters/common/request.js +5 -2
- package/packages/dd-trace/src/exporters/common/writer.js +24 -2
- package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
- package/packages/dd-trace/src/external-logger/src/index.js +4 -3
- package/packages/dd-trace/src/flush.js +102 -0
- package/packages/dd-trace/src/llmobs/experiments/client.js +10 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +301 -14
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +440 -112
- package/packages/dd-trace/src/llmobs/experiments/index.js +68 -44
- package/packages/dd-trace/src/llmobs/experiments/noop.js +103 -12
- package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +81 -4
- package/packages/dd-trace/src/llmobs/index.js +80 -17
- package/packages/dd-trace/src/llmobs/noop.js +2 -1
- package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
- package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
- package/packages/dd-trace/src/llmobs/writers/base.js +60 -24
- package/packages/dd-trace/src/noop/span.js +5 -0
- package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
- package/packages/dd-trace/src/openfeature/index.js +7 -4
- package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
- package/packages/dd-trace/src/openfeature/writers/exposures.js +70 -15
- package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
- package/packages/dd-trace/src/opentelemetry/logs/index.js +4 -1
- package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +3 -1
- package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +4 -2
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +4 -2
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
- package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +429 -352
- package/packages/dd-trace/src/opentracing/span.js +28 -0
- package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
- package/packages/dd-trace/src/plugin_manager.js +2 -0
- package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
- package/packages/dd-trace/src/plugins/util/test.js +74 -21
- package/packages/dd-trace/src/plugins/util/web.js +2 -65
- package/packages/dd-trace/src/profiling/oom.js +4 -3
- package/packages/dd-trace/src/profiling/profiler.js +7 -0
- package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
- package/packages/dd-trace/src/proxy.js +36 -8
- package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
- package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +17 -6
- package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +55 -0
- package/packages/dd-trace/src/serverless/vercel.js +122 -0
- package/packages/dd-trace/src/serverless.js +37 -20
- package/packages/dd-trace/src/span_stats.js +40 -3
- package/packages/dd-trace/src/telemetry/metrics.js +62 -8
- package/packages/dd-trace/src/telemetry/send-data.js +1 -0
- package/packages/dd-trace/src/tracer.js +22 -0
- package/packages/dd-trace/src/web-tags-cache.js +67 -13
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/https-proxy-agent/LICENSE +22 -0
- package/vendor/dist/https-proxy-agent/index.js +4 -0
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/proxy-from-env/LICENSE +20 -0
- package/vendor/dist/proxy-from-env/index.js +1 -0
|
@@ -1,17 +1,40 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { randomUUID } = require('node:crypto')
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const snapshotPayload = require('../../../../../vendor/dist/rfdc')({ proto: false, circles: false })
|
|
5
|
+
|
|
6
|
+
/** @typedef {{add?: string[], remove?: string[], replace?: string[]}} TagOperations */
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {object} DatasetRecordNew
|
|
9
|
+
* @property {string} [id]
|
|
10
|
+
* @property {unknown} inputData
|
|
11
|
+
* @property {unknown} [expectedOutput]
|
|
12
|
+
* @property {Record<string, unknown>} [metadata]
|
|
13
|
+
* @property {string[]} [tags]
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {object} PendingBatch
|
|
17
|
+
* @property {object} attributes
|
|
18
|
+
* @property {string[]} deleteRecordIds
|
|
19
|
+
* @property {Map<string, object>} insertPayloads
|
|
20
|
+
* @property {Map<string, object>} updatePayloads
|
|
21
|
+
* @property {number} totalCount
|
|
22
|
+
* @property {Map<string, TagOperations>} [inFlightTagOperations]
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const { tagOperationsAreEmpty, validateTagsList } = require('./util')
|
|
26
|
+
|
|
27
|
+
// Dataset record: { input, expectedOutput?, metadata?, id, tags? }.
|
|
6
28
|
// IDs are generated locally unless the caller supplies one.
|
|
7
29
|
class DatasetRecord {
|
|
8
|
-
constructor (input, expectedOutput = null, metadata = {}, id = null) {
|
|
30
|
+
constructor (input, expectedOutput = null, metadata = {}, id = null, tags = []) {
|
|
9
31
|
if (id != null && (typeof id !== 'string' || id.length === 0)) {
|
|
10
32
|
throw new Error('record id must be a non-empty string')
|
|
11
33
|
}
|
|
12
34
|
this.input = input
|
|
13
35
|
this.expectedOutput = expectedOutput ?? null
|
|
14
36
|
this.metadata = metadata ?? {}
|
|
37
|
+
this.tags = validateTagsList(tags)
|
|
15
38
|
this.id = id ?? randomUUID()
|
|
16
39
|
}
|
|
17
40
|
}
|
|
@@ -21,12 +44,14 @@ function versionFromMutationResult (result) {
|
|
|
21
44
|
}
|
|
22
45
|
|
|
23
46
|
function serializedRecord (record) {
|
|
24
|
-
|
|
47
|
+
const output = {
|
|
25
48
|
id: record.id,
|
|
26
49
|
input: record.input,
|
|
27
50
|
expected_output: record.expectedOutput ?? null,
|
|
28
51
|
metadata: record.metadata ?? {},
|
|
29
52
|
}
|
|
53
|
+
if (record.tags.length > 0) output.tags = record.tags
|
|
54
|
+
return output
|
|
30
55
|
}
|
|
31
56
|
|
|
32
57
|
function serializedRecordUpdate (update) {
|
|
@@ -36,9 +61,57 @@ function serializedRecordUpdate (update) {
|
|
|
36
61
|
output.expected_output = update.expectedOutput
|
|
37
62
|
}
|
|
38
63
|
if (Object.hasOwn(update, 'metadata') && update.metadata !== undefined) output.metadata = update.metadata
|
|
64
|
+
if (Object.hasOwn(update, 'tagOperations')) {
|
|
65
|
+
output.tag_operations = serializedTagOperations(update.tagOperations)
|
|
66
|
+
}
|
|
67
|
+
return output
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function serializedTagOperations (operations) {
|
|
71
|
+
const output = {}
|
|
72
|
+
if (Object.hasOwn(operations, 'add')) output.add = operations.add
|
|
73
|
+
if (Object.hasOwn(operations, 'remove')) output.remove = operations.remove
|
|
74
|
+
if (Object.hasOwn(operations, 'replace')) output.set = operations.replace
|
|
39
75
|
return output
|
|
40
76
|
}
|
|
41
77
|
|
|
78
|
+
/**
|
|
79
|
+
* @param {TagOperations} operations
|
|
80
|
+
* @returns {TagOperations}
|
|
81
|
+
*/
|
|
82
|
+
function copyTagOperations (operations) {
|
|
83
|
+
return Object.fromEntries(Object.entries(operations).map(([key, tags]) => [key, [...tags]]))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function mergeTagOperations (operations, operation, tags) {
|
|
87
|
+
if (operation === 'replace') return { replace: [...tags] }
|
|
88
|
+
if (Object.hasOwn(operations, 'replace')) {
|
|
89
|
+
const replaced = new Set(operations.replace)
|
|
90
|
+
for (const tag of tags) {
|
|
91
|
+
if (operation === 'add') replaced.add(tag)
|
|
92
|
+
else replaced.delete(tag)
|
|
93
|
+
}
|
|
94
|
+
return { replace: [...replaced].sort() }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const add = new Set(operations.add)
|
|
98
|
+
const remove = new Set(operations.remove)
|
|
99
|
+
for (const tag of tags) {
|
|
100
|
+
if (operation === 'add') {
|
|
101
|
+
if (remove.has(tag)) remove.delete(tag)
|
|
102
|
+
else add.add(tag)
|
|
103
|
+
} else if (add.has(tag)) {
|
|
104
|
+
add.delete(tag)
|
|
105
|
+
} else {
|
|
106
|
+
remove.add(tag)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const merged = {}
|
|
110
|
+
if (add.size > 0) merged.add = [...add].sort()
|
|
111
|
+
if (remove.size > 0) merged.remove = [...remove].sort()
|
|
112
|
+
return merged
|
|
113
|
+
}
|
|
114
|
+
|
|
42
115
|
function valuesAreEqual (left, right) {
|
|
43
116
|
return JSON.stringify(left) === JSON.stringify(right)
|
|
44
117
|
}
|
|
@@ -50,6 +123,9 @@ function updateFromInsertedRecord (recordId, record, payload) {
|
|
|
50
123
|
update.expectedOutput = record.expectedOutput
|
|
51
124
|
}
|
|
52
125
|
if (!valuesAreEqual(record.metadata, payload.metadata)) update.metadata = record.metadata
|
|
126
|
+
if (!valuesAreEqual(record.tags, payload.tags ?? [])) {
|
|
127
|
+
update.tagOperations = { replace: [...record.tags] }
|
|
128
|
+
}
|
|
53
129
|
return update
|
|
54
130
|
}
|
|
55
131
|
|
|
@@ -63,21 +139,25 @@ class Dataset {
|
|
|
63
139
|
#newRecordsById
|
|
64
140
|
#updatedRecordsById
|
|
65
141
|
#deletedRecordIds
|
|
142
|
+
#pendingTagOperations
|
|
66
143
|
#id
|
|
67
144
|
#projectId
|
|
68
145
|
#version
|
|
69
146
|
#latestVersion
|
|
70
147
|
#pushPromise
|
|
148
|
+
#filterTags
|
|
71
149
|
|
|
72
|
-
constructor (client, name, description = '') {
|
|
150
|
+
constructor (client, name, description = '', filterTags = []) {
|
|
73
151
|
this.#client = client
|
|
74
152
|
this.#name = name
|
|
75
153
|
this.#description = description
|
|
154
|
+
this.#filterTags = validateTagsList(filterTags)
|
|
76
155
|
this.#records = []
|
|
77
156
|
this.#recordsById = new Map()
|
|
78
157
|
this.#newRecordsById = new Map()
|
|
79
158
|
this.#updatedRecordsById = new Map()
|
|
80
159
|
this.#deletedRecordIds = new Set()
|
|
160
|
+
this.#pendingTagOperations = new Map()
|
|
81
161
|
this.#id = null
|
|
82
162
|
this.#projectId = null
|
|
83
163
|
this.#version = null
|
|
@@ -86,8 +166,8 @@ class Dataset {
|
|
|
86
166
|
}
|
|
87
167
|
|
|
88
168
|
// Build a Dataset that already exists remotely (used by pullDataset).
|
|
89
|
-
static fromExisting (client, name, description, id, projectId, records, version, latestVersion) {
|
|
90
|
-
const dataset = new Dataset(client, name, description)
|
|
169
|
+
static fromExisting (client, name, description, id, projectId, records, version, latestVersion, filterTags) {
|
|
170
|
+
const dataset = new Dataset(client, name, description, filterTags)
|
|
91
171
|
dataset.#id = id
|
|
92
172
|
dataset.#projectId = projectId
|
|
93
173
|
dataset.#version = version ?? null
|
|
@@ -97,14 +177,100 @@ class Dataset {
|
|
|
97
177
|
}
|
|
98
178
|
|
|
99
179
|
// Append a record. Accepts a DatasetRecord or (input, expectedOutput?, metadata?).
|
|
100
|
-
addRecord (recordOrInput, expectedOutput, metadata) {
|
|
180
|
+
addRecord (recordOrInput, expectedOutput, metadata, tags) {
|
|
101
181
|
const record = recordOrInput instanceof DatasetRecord
|
|
102
182
|
? recordOrInput
|
|
103
|
-
: new DatasetRecord(recordOrInput, expectedOutput, metadata)
|
|
183
|
+
: new DatasetRecord(recordOrInput, expectedOutput, metadata, null, tags)
|
|
104
184
|
this.#addRecord(record)
|
|
105
185
|
return this
|
|
106
186
|
}
|
|
107
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Add multiple records to a dataset.
|
|
190
|
+
* @param {DatasetRecordNew[]} records
|
|
191
|
+
* @returns {Dataset} This dataset for chaining.
|
|
192
|
+
*/
|
|
193
|
+
addRecords (records) {
|
|
194
|
+
const newRecords = []
|
|
195
|
+
const recordIds = new Set(this.#recordsById.keys())
|
|
196
|
+
|
|
197
|
+
// Construct and validate the entire batch before mutating the dataset.
|
|
198
|
+
for (const record of records) {
|
|
199
|
+
if (record.id !== undefined && (typeof record.id !== 'string' || record.id.length === 0)) {
|
|
200
|
+
throw new Error('record id must be a non-empty string')
|
|
201
|
+
}
|
|
202
|
+
const newRecord = new DatasetRecord(
|
|
203
|
+
record.inputData,
|
|
204
|
+
record.expectedOutput,
|
|
205
|
+
record.metadata,
|
|
206
|
+
record.id,
|
|
207
|
+
record.tags
|
|
208
|
+
)
|
|
209
|
+
if (recordIds.has(newRecord.id)) throw new Error(`Duplicate record id '${newRecord.id}'`)
|
|
210
|
+
recordIds.add(newRecord.id)
|
|
211
|
+
newRecords.push(newRecord)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const record of newRecords) this.#addRecord(record)
|
|
215
|
+
return this
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Add tags to a dataset record.
|
|
220
|
+
* @param {number} index Dataset record index.
|
|
221
|
+
* @param {string[]} tags Tags in key:value format.
|
|
222
|
+
* @returns {Dataset} This dataset for chaining.
|
|
223
|
+
*/
|
|
224
|
+
addTags (index, tags) {
|
|
225
|
+
const validated = validateTagsList(tags)
|
|
226
|
+
const record = this.#recordAt(index)
|
|
227
|
+
const next = new Set(record.tags)
|
|
228
|
+
const added = []
|
|
229
|
+
for (const tag of validated) {
|
|
230
|
+
if (next.has(tag)) continue
|
|
231
|
+
next.add(tag)
|
|
232
|
+
added.push(tag)
|
|
233
|
+
}
|
|
234
|
+
record.tags = [...next].sort()
|
|
235
|
+
this.#queueTagOperation(record.id, 'add', added)
|
|
236
|
+
return this
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Remove tags from a dataset record.
|
|
241
|
+
* @param {number} index Dataset record index.
|
|
242
|
+
* @param {string[]} tags Tags in key:value format.
|
|
243
|
+
* @returns {Dataset} This dataset for chaining.
|
|
244
|
+
*/
|
|
245
|
+
removeTags (index, tags) {
|
|
246
|
+
const validated = validateTagsList(tags)
|
|
247
|
+
const record = this.#recordAt(index)
|
|
248
|
+
const next = new Set(record.tags)
|
|
249
|
+
const removed = []
|
|
250
|
+
for (const tag of validated) {
|
|
251
|
+
if (!next.has(tag)) continue
|
|
252
|
+
next.delete(tag)
|
|
253
|
+
removed.push(tag)
|
|
254
|
+
}
|
|
255
|
+
record.tags = [...next].sort()
|
|
256
|
+
this.#queueTagOperation(record.id, 'remove', removed)
|
|
257
|
+
return this
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Replace all tags on a dataset record.
|
|
262
|
+
* @param {number} index Dataset record index.
|
|
263
|
+
* @param {string[]} tags Tags in key:value format.
|
|
264
|
+
* @returns {Dataset} This dataset for chaining.
|
|
265
|
+
*/
|
|
266
|
+
replaceTags (index, tags) {
|
|
267
|
+
const validated = validateTagsList(tags)
|
|
268
|
+
const record = this.#recordAt(index)
|
|
269
|
+
record.tags = validated
|
|
270
|
+
this.#queueTagOperation(record.id, 'replace', validated)
|
|
271
|
+
return this
|
|
272
|
+
}
|
|
273
|
+
|
|
108
274
|
/**
|
|
109
275
|
* Update an existing dataset record. New records are updated in place and are sent with their insert.
|
|
110
276
|
* @param {number} index Dataset record index.
|
|
@@ -130,6 +296,9 @@ class Dataset {
|
|
|
130
296
|
|
|
131
297
|
const update = this.#updatedRecordsById.get(record.id) ?? { id: record.id }
|
|
132
298
|
for (const field of providedFields) update[field] = record[field]
|
|
299
|
+
if (this.#pendingTagOperations.has(record.id)) {
|
|
300
|
+
update.tagOperations = this.#pendingTagOperations.get(record.id)
|
|
301
|
+
}
|
|
133
302
|
this.#updatedRecordsById.set(record.id, update)
|
|
134
303
|
return this
|
|
135
304
|
}
|
|
@@ -144,13 +313,47 @@ class Dataset {
|
|
|
144
313
|
this.#records.splice(index, 1)
|
|
145
314
|
this.#recordsById.delete(record.id)
|
|
146
315
|
|
|
147
|
-
if (this.#newRecordsById.delete(record.id))
|
|
316
|
+
if (this.#newRecordsById.delete(record.id)) {
|
|
317
|
+
this.#pendingTagOperations.delete(record.id)
|
|
318
|
+
return this
|
|
319
|
+
}
|
|
148
320
|
|
|
149
321
|
this.#updatedRecordsById.delete(record.id)
|
|
322
|
+
this.#pendingTagOperations.delete(record.id)
|
|
150
323
|
this.#deletedRecordIds.add(record.id)
|
|
151
324
|
return this
|
|
152
325
|
}
|
|
153
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Queue a tag operation for the next dataset push.
|
|
329
|
+
* @param {string} recordId Dataset record id.
|
|
330
|
+
* @param {'add' | 'remove' | 'replace'} operation Tag operation to queue.
|
|
331
|
+
* @param {string[]} tags Tags in key:value format.
|
|
332
|
+
* @returns {void}
|
|
333
|
+
*/
|
|
334
|
+
#queueTagOperation (recordId, operation, tags) {
|
|
335
|
+
if (operation !== 'replace' && tags.length === 0) return
|
|
336
|
+
const operations = mergeTagOperations(this.#pendingTagOperations.get(recordId) ?? {}, operation, tags)
|
|
337
|
+
if (tagOperationsAreEmpty(operations)) {
|
|
338
|
+
this.#pendingTagOperations.delete(recordId)
|
|
339
|
+
const update = this.#updatedRecordsById.get(recordId)
|
|
340
|
+
if (update) {
|
|
341
|
+
const hasRecordUpdate = Object.hasOwn(update, 'input') ||
|
|
342
|
+
Object.hasOwn(update, 'expectedOutput') ||
|
|
343
|
+
Object.hasOwn(update, 'metadata')
|
|
344
|
+
delete update.tagOperations
|
|
345
|
+
if (!hasRecordUpdate) this.#updatedRecordsById.delete(recordId)
|
|
346
|
+
}
|
|
347
|
+
return
|
|
348
|
+
}
|
|
349
|
+
this.#pendingTagOperations.set(recordId, operations)
|
|
350
|
+
if (!this.#newRecordsById.has(recordId)) {
|
|
351
|
+
const update = this.#updatedRecordsById.get(recordId) ?? { id: recordId }
|
|
352
|
+
update.tagOperations = operations
|
|
353
|
+
this.#updatedRecordsById.set(recordId, update)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
154
357
|
name () {
|
|
155
358
|
return this.#name
|
|
156
359
|
}
|
|
@@ -175,6 +378,10 @@ class Dataset {
|
|
|
175
378
|
return this.#projectId
|
|
176
379
|
}
|
|
177
380
|
|
|
381
|
+
projectName () {
|
|
382
|
+
return this.#client.projectName
|
|
383
|
+
}
|
|
384
|
+
|
|
178
385
|
version () {
|
|
179
386
|
return this.#version
|
|
180
387
|
}
|
|
@@ -183,6 +390,14 @@ class Dataset {
|
|
|
183
390
|
return this.#latestVersion
|
|
184
391
|
}
|
|
185
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Return the tags used to filter this dataset.
|
|
395
|
+
* @returns {string[]} Dataset record filter tags.
|
|
396
|
+
*/
|
|
397
|
+
filterTags () {
|
|
398
|
+
return [...this.#filterTags]
|
|
399
|
+
}
|
|
400
|
+
|
|
186
401
|
// Dashboard URL for this dataset, or null until pushed/pulled.
|
|
187
402
|
url () {
|
|
188
403
|
if (this.#id === null) return null
|
|
@@ -228,10 +443,13 @@ class Dataset {
|
|
|
228
443
|
const pending = this.#pendingBatch()
|
|
229
444
|
if (pending.totalCount === 0) return { pushedCount: 0, totalCount: 0 }
|
|
230
445
|
|
|
446
|
+
this.#detachCommittedTagOperations(pending)
|
|
447
|
+
|
|
231
448
|
let result
|
|
232
449
|
try {
|
|
233
450
|
result = await this.#client.batchUpdateDatasetRecords(projectId, this.#id, pending.attributes)
|
|
234
451
|
} catch (err) {
|
|
452
|
+
this.#restoreFailedTagOperations(pending)
|
|
235
453
|
throw new Error(`Failed to push changes to dataset '${this.#name}': ${err.message}`)
|
|
236
454
|
}
|
|
237
455
|
|
|
@@ -244,7 +462,7 @@ class Dataset {
|
|
|
244
462
|
const insertRecords = []
|
|
245
463
|
const insertPayloads = new Map()
|
|
246
464
|
for (const [recordId, record] of this.#newRecordsById) {
|
|
247
|
-
const payload = serializedRecord(record)
|
|
465
|
+
const payload = snapshotPayload(serializedRecord(record))
|
|
248
466
|
insertRecords.push(payload)
|
|
249
467
|
insertPayloads.set(recordId, payload)
|
|
250
468
|
}
|
|
@@ -252,7 +470,10 @@ class Dataset {
|
|
|
252
470
|
const updateRecords = []
|
|
253
471
|
const updatePayloads = new Map()
|
|
254
472
|
for (const [recordId, update] of this.#updatedRecordsById) {
|
|
255
|
-
const
|
|
473
|
+
const tagOperations = this.#pendingTagOperations.get(recordId)
|
|
474
|
+
if (tagOperations) update.tagOperations = tagOperations
|
|
475
|
+
else delete update.tagOperations
|
|
476
|
+
const payload = snapshotPayload(serializedRecordUpdate(update))
|
|
256
477
|
updateRecords.push(payload)
|
|
257
478
|
updatePayloads.set(recordId, payload)
|
|
258
479
|
}
|
|
@@ -277,6 +498,58 @@ class Dataset {
|
|
|
277
498
|
}
|
|
278
499
|
}
|
|
279
500
|
|
|
501
|
+
/**
|
|
502
|
+
* Detach tag changes sent by this batch so edits made while the request is in flight
|
|
503
|
+
* are queued relative to the response that this batch will commit.
|
|
504
|
+
* @param {PendingBatch} pending
|
|
505
|
+
* @returns {void}
|
|
506
|
+
*/
|
|
507
|
+
#detachCommittedTagOperations (pending) {
|
|
508
|
+
pending.inFlightTagOperations = new Map()
|
|
509
|
+
for (const [recordId] of pending.insertPayloads) {
|
|
510
|
+
const operations = this.#pendingTagOperations.get(recordId)
|
|
511
|
+
if (!operations) continue
|
|
512
|
+
pending.inFlightTagOperations.set(recordId, copyTagOperations(operations))
|
|
513
|
+
this.#pendingTagOperations.delete(recordId)
|
|
514
|
+
}
|
|
515
|
+
for (const [recordId, payload] of pending.updatePayloads) {
|
|
516
|
+
const operations = this.#pendingTagOperations.get(recordId)
|
|
517
|
+
if (!operations || !Object.hasOwn(payload, 'tag_operations')) continue
|
|
518
|
+
|
|
519
|
+
pending.inFlightTagOperations.set(recordId, copyTagOperations(operations))
|
|
520
|
+
this.#pendingTagOperations.delete(recordId)
|
|
521
|
+
const update = this.#updatedRecordsById.get(recordId)
|
|
522
|
+
if (update) delete update.tagOperations
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Restore tag changes when a batch request fails, including edits made while it was in flight.
|
|
528
|
+
* @param {PendingBatch} pending
|
|
529
|
+
* @returns {void}
|
|
530
|
+
*/
|
|
531
|
+
#restoreFailedTagOperations (pending) {
|
|
532
|
+
if (!pending.inFlightTagOperations) return
|
|
533
|
+
for (const [recordId, operations] of pending.inFlightTagOperations) {
|
|
534
|
+
const record = this.#recordsById.get(recordId)
|
|
535
|
+
if (!record || this.#newRecordsById.has(recordId) || this.#deletedRecordIds.has(recordId)) continue
|
|
536
|
+
|
|
537
|
+
const update = this.#updatedRecordsById.get(recordId) ?? { id: recordId }
|
|
538
|
+
const queuedOperations = this.#pendingTagOperations.get(recordId)
|
|
539
|
+
const restoredOperations = queuedOperations
|
|
540
|
+
? { replace: [...record.tags] }
|
|
541
|
+
: copyTagOperations(operations)
|
|
542
|
+
this.#pendingTagOperations.set(recordId, restoredOperations)
|
|
543
|
+
update.tagOperations = restoredOperations
|
|
544
|
+
this.#updatedRecordsById.set(recordId, update)
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Clear the changes represented by a completed batch while retaining concurrent local edits.
|
|
550
|
+
* @param {PendingBatch} pending
|
|
551
|
+
* @returns {void}
|
|
552
|
+
*/
|
|
280
553
|
#clearCommittedChanges (pending) {
|
|
281
554
|
for (const [recordId, payload] of pending.insertPayloads) {
|
|
282
555
|
const current = this.#newRecordsById.get(recordId)
|
|
@@ -286,19 +559,32 @@ class Dataset {
|
|
|
286
559
|
}
|
|
287
560
|
if (valuesAreEqual(serializedRecord(current), payload)) {
|
|
288
561
|
this.#newRecordsById.delete(recordId)
|
|
562
|
+
this.#pendingTagOperations.delete(recordId)
|
|
289
563
|
continue
|
|
290
564
|
}
|
|
291
565
|
|
|
292
566
|
this.#newRecordsById.delete(recordId)
|
|
293
567
|
const update = this.#updatedRecordsById.get(recordId) ??
|
|
294
568
|
updateFromInsertedRecord(recordId, current, payload)
|
|
569
|
+
const queuedOperations = this.#pendingTagOperations.get(recordId)
|
|
570
|
+
if (queuedOperations) update.tagOperations = queuedOperations
|
|
571
|
+
if (update.tagOperations) {
|
|
572
|
+
this.#pendingTagOperations.set(recordId, copyTagOperations(update.tagOperations))
|
|
573
|
+
}
|
|
295
574
|
this.#updatedRecordsById.set(recordId, update)
|
|
296
575
|
}
|
|
297
576
|
|
|
298
577
|
for (const [recordId, payload] of pending.updatePayloads) {
|
|
299
578
|
const current = this.#updatedRecordsById.get(recordId)
|
|
300
|
-
|
|
579
|
+
const queuedOperations = this.#pendingTagOperations.get(recordId)
|
|
580
|
+
if (!current || queuedOperations) continue
|
|
581
|
+
|
|
582
|
+
const comparison = { ...current }
|
|
583
|
+
const committedOperations = pending.inFlightTagOperations?.get(recordId)
|
|
584
|
+
if (committedOperations) comparison.tagOperations = committedOperations
|
|
585
|
+
if (valuesAreEqual(serializedRecordUpdate(comparison), payload)) {
|
|
301
586
|
this.#updatedRecordsById.delete(recordId)
|
|
587
|
+
this.#pendingTagOperations.delete(recordId)
|
|
302
588
|
}
|
|
303
589
|
}
|
|
304
590
|
|
|
@@ -341,7 +627,8 @@ class Dataset {
|
|
|
341
627
|
record.input,
|
|
342
628
|
record.expectedOutput,
|
|
343
629
|
record.metadata,
|
|
344
|
-
record.id
|
|
630
|
+
record.id,
|
|
631
|
+
record.tags
|
|
345
632
|
)
|
|
346
633
|
}
|
|
347
634
|
if (this.#recordsById.has(record.id)) throw new Error(`Duplicate record id '${record.id}'`)
|