diff --git a/crates/clickhouse-cloud-api/README.md b/crates/clickhouse-cloud-api/README.md index e5f647400..ecdec999f 100644 --- a/crates/clickhouse-cloud-api/README.md +++ b/crates/clickhouse-cloud-api/README.md @@ -6,6 +6,12 @@ Requires Rust 1.94 or later. ## Updated Cloud API surface +`ClickStackLogSource` and `ClickStackLogSourceResponse` gain optional +`event_name_expression` and `severity_number_expression`, the expressions for +the OpenTelemetry log `EventName` and numeric `SeverityNumber` (1-24). `TryFrom` +carries them back to the request type. Struct-literal callers need the new +fields as `None` or `..Default::default()`. + `ClickPipeMongoDBPipeTableMapping` gains optional `mongo_config` (`ClickPipeMongoDBTableConfig`), per-collection MongoDB options. Its one field, the required `persist_deleted_documents`, keeps the full document as it was diff --git a/crates/clickhouse-cloud-api/src/convert/clickstack.rs b/crates/clickhouse-cloud-api/src/convert/clickstack.rs index 51ca7d8a3..9c6eab3aa 100644 --- a/crates/clickhouse-cloud-api/src/convert/clickstack.rs +++ b/crates/clickhouse-cloud-api/src/convert/clickstack.rs @@ -147,6 +147,7 @@ impl TryFrom for ClickStackLogSource { disabled: value.disabled, displayed_timestamp_value_expression: value.displayed_timestamp_value_expression, event_attributes_expression: value.event_attributes_expression, + event_name_expression: value.event_name_expression, filter_settings: value.filter_settings.map(TryInto::try_into).transpose()?, from: from.try_into()?, highlighted_row_attribute_expressions: value @@ -200,6 +201,7 @@ impl TryFrom for ClickStackLogSource { section: value.section, service_name_expression: value.service_name_expression, service_version_expression: value.service_version_expression, + severity_number_expression: value.severity_number_expression, severity_text_expression: value.severity_text_expression, span_id_expression: value.span_id_expression, timestamp_value_expression, diff --git a/crates/clickhouse-cloud-api/src/models/clickstack.rs b/crates/clickhouse-cloud-api/src/models/clickstack.rs index bc89c141a..a350e3739 100644 --- a/crates/clickhouse-cloud-api/src/models/clickstack.rs +++ b/crates/clickhouse-cloud-api/src/models/clickstack.rs @@ -3905,6 +3905,11 @@ pub struct ClickStackLogSource { skip_serializing_if = "Option::is_none" )] pub event_attributes_expression: Option, + #[serde( + rename = "eventNameExpression", + skip_serializing_if = "Option::is_none" + )] + pub event_name_expression: Option, #[serde(rename = "filterSettings", skip_serializing_if = "Option::is_none")] pub filter_settings: Option, pub from: ClickStackSourceFrom, @@ -3959,6 +3964,11 @@ pub struct ClickStackLogSource { skip_serializing_if = "Option::is_none" )] pub service_name_expression: Option, + #[serde( + rename = "severityNumberExpression", + skip_serializing_if = "Option::is_none" + )] + pub severity_number_expression: Option, #[serde( rename = "severityTextExpression", skip_serializing_if = "Option::is_none" @@ -4013,6 +4023,11 @@ pub struct ClickStackLogSourceResponse { skip_serializing_if = "Option::is_none" )] pub event_attributes_expression: Option, + #[serde( + rename = "eventNameExpression", + skip_serializing_if = "Option::is_none" + )] + pub event_name_expression: Option, #[serde(rename = "filterSettings", skip_serializing_if = "Option::is_none")] pub filter_settings: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -4070,6 +4085,11 @@ pub struct ClickStackLogSourceResponse { skip_serializing_if = "Option::is_none" )] pub service_name_expression: Option, + #[serde( + rename = "severityNumberExpression", + skip_serializing_if = "Option::is_none" + )] + pub severity_number_expression: Option, #[serde( rename = "severityTextExpression", skip_serializing_if = "Option::is_none" diff --git a/crates/clickhouse-cloud-api/tests/models_test.rs b/crates/clickhouse-cloud-api/tests/models_test.rs index 8f9f2a71a..b3fd0e3a8 100644 --- a/crates/clickhouse-cloud-api/tests/models_test.rs +++ b/crates/clickhouse-cloud-api/tests/models_test.rs @@ -6919,9 +6919,10 @@ fn deserialize_clickstack_webhook_unknown_service_round_trips() { #[test] fn clickstack_source_expression_fields_missing_or_null_are_none() { - // `orderByExpression` (log, trace), `resourceAttributesExpression` - // (session), and the trace-only display, sampling and span-link - // expressions are optional in the spec: absent or `null` lands as `None` + // `orderByExpression` (log, trace), the log-only OpenTelemetry + // `eventNameExpression` and `severityNumberExpression`, + // `resourceAttributesExpression` (session), and the trace-only display, + // sampling and span-link expressions are optional in the spec: absent or `null` lands as `None` // on the response variants and is omitted from the request variants. let log = serde_json::json!({ "kind": "log", @@ -6967,9 +6968,17 @@ fn clickstack_source_expression_fields_missing_or_null_are_none() { "spanLinksValueExpression", ]; - for payload in [log.clone(), with_nulls(&log, &["orderByExpression"])] { + let log_keys = [ + "eventNameExpression", + "orderByExpression", + "severityNumberExpression", + ]; + + for payload in [log.clone(), with_nulls(&log, &log_keys)] { let response: ClickStackLogSourceResponse = serde_json::from_value(payload).unwrap(); + assert_eq!(response.event_name_expression, None); assert_eq!(response.order_by_expression, None); + assert_eq!(response.severity_number_expression, None); let request = ClickStackLogSource::try_from(response).unwrap(); assert_eq!(serde_json::to_value(&request).unwrap(), log); } @@ -6996,11 +7005,15 @@ fn clickstack_source_expression_fields_missing_or_null_are_none() { #[test] fn clickstack_source_expression_fields_serialize_with_their_wire_names() { let log = ClickStackLogSource { + event_name_expression: Some("EventName".to_string()), order_by_expression: Some("Timestamp DESC".to_string()), + severity_number_expression: Some("SeverityNumber".to_string()), ..Default::default() }; let v = serde_json::to_value(&log).unwrap(); + assert_eq!(v["eventNameExpression"], "EventName"); assert_eq!(v["orderByExpression"], "Timestamp DESC"); + assert_eq!(v["severityNumberExpression"], "SeverityNumber"); let session = ClickStackSessionSource { resource_attributes_expression: Some("ResourceAttributes".to_string()), @@ -7023,6 +7036,34 @@ fn clickstack_source_expression_fields_serialize_with_their_wire_names() { assert_eq!(v["spanLinksValueExpression"], "Links"); } +#[test] +fn clickstack_log_source_otel_expressions_write_back() { + let wire = serde_json::json!({ + "kind": "log", + "name": "logs", + "connection": "conn-1", + "defaultTableSelectExpression": "*", + "from": {"databaseName": "default", "tableName": "logs"}, + "timestampValueExpression": "Timestamp", + "eventNameExpression": "EventName", + "severityNumberExpression": "SeverityNumber", + "severityTextExpression": "SeverityText", + }); + let response: ClickStackLogSourceResponse = serde_json::from_value(wire.clone()).unwrap(); + assert_eq!(response.event_name_expression.as_deref(), Some("EventName")); + assert_eq!( + response.severity_number_expression.as_deref(), + Some("SeverityNumber") + ); + let request = ClickStackLogSource::try_from(response).unwrap(); + assert_eq!(request.event_name_expression.as_deref(), Some("EventName")); + assert_eq!( + request.severity_number_expression.as_deref(), + Some("SeverityNumber") + ); + assert_eq!(serde_json::to_value(&request).unwrap(), wire); +} + #[test] fn clickstack_validate_webhook_response_null_fields_are_none() { let null: ClickStackValidateWebhookResponse =