Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.

# Unreleased

- Preserve per-attribute `requirement_level` on attribute refs of public attribute groups in the v2 resolved and materialized schemas. ([#TODO](https://github.com/open-telemetry/weaver/pull/TODO)
Comment thread
lmolkova marked this conversation as resolved.
Outdated
- Fix panic when a registry, policy, or template path uses a commit SHA. ([#1414](https://github.com/open-telemetry/weaver/pull/1414))
- Add a stats dashboard with charts to the `serve` UI. ([#1570](https://github.com/open-telemetry/weaver/pull/1570) by @jerbly)
- Add `semconv_grouped_entities` JQ helper. ([#1560](https://github.com/open-telemetry/weaver/pull/1560) by @lmolkova)
Expand Down
24 changes: 22 additions & 2 deletions crates/weaver_forge/src/v2/attribute_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
use crate::v2::provenance::Provenance;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use weaver_semconv::v2::{signal_id::SignalId, CommonFields};
use weaver_semconv::{
attribute::RequirementLevel,
v2::{signal_id::SignalId, CommonFields},
};

use crate::v2::attribute::Attribute;

Expand All @@ -14,7 +17,7 @@ pub struct AttributeGroup {
/// The name of the attribute group, must be unique.
pub id: SignalId,
/// List of attributes.
pub attributes: Vec<Attribute>,
pub attributes: Vec<AttributeGroupAttribute>,
/// Common fields (like brief, note, annotations).
#[serde(flatten)]
pub common: CommonFields,
Expand All @@ -23,3 +26,20 @@ pub struct AttributeGroup {
#[serde(skip_serializing_if = "Provenance::is_empty")]
pub provenance: Provenance,
}

/// An attribute belonging to a public attribute group, carrying the
/// group-specific requirement level.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(deny_unknown_fields)]
pub struct AttributeGroupAttribute {
/// Base attribute definition.
#[serde(flatten)]
pub base: Attribute,
/// Specifies if the attribute is mandatory. Can be "required",
/// "conditionally_required", "recommended" or "opt_in". When omitted,
/// the attribute is "recommended". When set to
/// "conditionally_required", the string provided as `condition` MUST
/// specify the conditions under which the attribute is required.
Comment thread
lmolkova marked this conversation as resolved.
Outdated
pub requirement_level: RequirementLevel,
}
43 changes: 34 additions & 9 deletions crates/weaver_forge/src/v2/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
error::Error,
v2::{
attribute::Attribute,
attribute_group::AttributeGroup,
attribute_group::{AttributeGroup, AttributeGroupAttribute},
entity::{Entity, EntityAttribute},
event::{Event, EventAttribute, EventRefinement},
metric::{Metric, MetricAttribute, MetricRefinement},
Expand Down Expand Up @@ -422,17 +422,20 @@ impl ForgeResolvedRegistry {
.attributes
.iter()
.filter_map(|ar| {
let attr = attribute_lookup(ar).map(|a| Attribute {
key: a.key.clone(),
r#type: a.r#type.clone(),
examples: a.examples.clone(),
common: a.common.clone(),
provenance: resolve_provenance(&a.provenance),
let attr = attribute_lookup(&ar.base).map(|a| AttributeGroupAttribute {
base: Attribute {
key: a.key.clone(),
r#type: a.r#type.clone(),
examples: a.examples.clone(),
common: a.common.clone(),
provenance: resolve_provenance(&a.provenance),
},
requirement_level: ar.requirement_level.clone(),
});
if attr.is_none() {
errors.push(Error::AttributeNotFound {
group_id: format!("attribute_group.{}", &ag.id),
attr_ref: AttributeRef(ar.0),
attr_ref: AttributeRef(ar.base.0),
});
}
attr
Expand Down Expand Up @@ -567,7 +570,17 @@ mod tests {
common: CommonFields::default(),
provenance: Default::default(),
}],
attribute_groups: vec![],
attribute_groups: vec![v2::attribute_group::AttributeGroup {
id: SignalId::from("my-group".to_owned()),
attributes: vec![v2::attribute_group::AttributeGroupAttributeRef {
base: attribute::AttributeRef(0),
requirement_level: weaver_semconv::attribute::RequirementLevel::Basic(
weaver_semconv::attribute::BasicRequirementLevelSpec::Required,
),
}],
common: CommonFields::default(),
provenance: Default::default(),
}],
},
refinements: v2::refinements::Refinements {
spans: vec![span::SpanRefinement {
Expand Down Expand Up @@ -636,6 +649,18 @@ mod tests {
assert_eq!(forge_registry.registry.metrics.len(), 1);
assert_eq!(forge_registry.registry.events.len(), 1);
assert_eq!(forge_registry.registry.entities.len(), 1);
assert_eq!(forge_registry.registry.attribute_groups.len(), 1);

let group = &forge_registry.registry.attribute_groups[0];
assert_eq!(group.id, "my-group".to_owned().into());
assert_eq!(group.attributes.len(), 1);
assert_eq!(group.attributes[0].base.key, "test.attr");
assert_eq!(
group.attributes[0].requirement_level,
weaver_semconv::attribute::RequirementLevel::Basic(
weaver_semconv::attribute::BasicRequirementLevelSpec::Required,
)
);
assert_eq!(forge_registry.refinements.spans.len(), 1);
assert_eq!(forge_registry.refinements.metrics.len(), 1);
assert_eq!(forge_registry.refinements.events.len(), 1);
Expand Down
23 changes: 21 additions & 2 deletions crates/weaver_resolved_schema/src/v2/attribute_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use weaver_semconv::v2::{signal_id::SignalId, CommonFields};
use weaver_semconv::{
attribute::RequirementLevel,
v2::{signal_id::SignalId, CommonFields},
};

use crate::v2::{attribute::AttributeRef, provenance::Provenance, Signal};

Expand All @@ -23,7 +26,7 @@ pub struct AttributeGroup {
pub id: SignalId,

/// List of attributes and group references that belong to this group
pub attributes: Vec<AttributeRef>,
pub attributes: Vec<AttributeGroupAttributeRef>,

/// Common fields (like brief, note, annotations).
#[serde(flatten)]
Expand All @@ -35,6 +38,22 @@ pub struct AttributeGroup {
pub provenance: Provenance,
}

/// A reference to an attribute in a public attribute group that remembers the
/// group-specific requirement level refinement.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(deny_unknown_fields)]
pub struct AttributeGroupAttributeRef {
/// Reference, by index, to the attribute catalog.
pub base: AttributeRef,
/// Specifies if the attribute is mandatory. Can be "required",
/// "conditionally_required", "recommended" or "opt_in". When omitted,
/// the attribute is "recommended". When set to
/// "conditionally_required", the string provided as `condition` MUST
/// specify the conditions under which the attribute is required.
Comment thread
lmolkova marked this conversation as resolved.
Outdated
pub requirement_level: RequirementLevel,
}

impl Signal for AttributeGroup {
fn id(&self) -> &str {
&self.id
Expand Down
78 changes: 77 additions & 1 deletion crates/weaver_resolved_schema/src/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,10 @@ pub fn convert_v1_to_v2(
// TODO - we need to check lineage and remove parent groups.
for attr in g.attributes.iter().filter_map(|a| c.attribute(a)) {
if let Some(a) = v2_catalog.convert_ref(attr) {
attributes.push(a);
attributes.push(attribute_group::AttributeGroupAttributeRef {
base: a,
requirement_level: attr.requirement_level.clone(),
});
} else {
// TODO logic error!
}
Expand Down Expand Up @@ -1190,6 +1193,79 @@ mod tests {
}
}

#[test]
fn test_convert_public_attribute_group_carries_requirement_level() {
use weaver_semconv::attribute::{BasicRequirementLevelSpec, RequirementLevel};

let mut builder = crate::catalog::test_utils::CatalogBuilder::default();
// The catalog attribute carries the requirement level that was
// resolved from the attribute ref refinement in the public group.
let ref0 = builder.add(
Attribute {
name: "test.key".to_owned(),
r#type: weaver_semconv::attribute::AttributeType::PrimitiveOrArray(
weaver_semconv::attribute::PrimitiveOrArrayTypeSpec::String,
),
brief: "".to_owned(),
examples: None,
tag: None,
requirement_level: RequirementLevel::Basic(BasicRequirementLevelSpec::Required),
sampling_relevant: None,
note: "".to_owned(),
stability: Some(Stability::Stable),
deprecated: None,
prefix: false,
tags: None,
annotations: None,
value: None,
role: None,
},
None,
);
let v1_catalog = builder.build();
let v1_registry = crate::registry::Registry {
registry_url: "my.schema.url".to_owned(),
groups: vec![Group {
id: "test.group".to_owned(),
r#type: GroupType::AttributeGroup,
brief: "a public group".to_owned(),
note: "".to_owned(),
prefix: "".to_owned(),
extends: None,
stability: Some(Stability::Stable),
deprecated: None,
attributes: vec![ref0],
span_kind: None,
events: vec![],
metric_name: None,
instrument: None,
unit: None,
requirement_level: None,
name: None,
lineage: None,
display_name: None,
body: None,
annotations: None,
entity_associations: vec![],
visibility: Some(AttributeGroupVisibilitySpec::Public),
is_v2: true,
span_name: None,
}],
};
let (_, v2_registry, _, _) = convert_v1_to_v2(v1_catalog, v1_registry, BTreeSet::new())
.expect("Failed to convert v1 to v2");
// The public attribute group is emitted...
assert_eq!(v2_registry.attribute_groups.len(), 1);
let group = &v2_registry.attribute_groups[0];
assert_eq!(group.id, "test.group".to_owned().into());
// ...and its attribute ref carries the group-specific requirement level.
assert_eq!(group.attributes.len(), 1);
assert_eq!(
group.attributes[0].requirement_level,
RequirementLevel::Basic(BasicRequirementLevelSpec::Required)
);
}

#[test]
fn test_try_from_v1_to_v2() {
let mut dependencies = BTreeSet::new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ registry:
attribute_groups:
- id: imported.group.a
attributes:
- 1
- base: 1
requirement_level: recommended
brief: a fun group
note: a group note
stability: stable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ registry:
attribute_groups:
- id: local.group
attributes:
- 1
- base: 1
requirement_level: required
brief: local group
stability: stable
spans:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ registry:
attribute_groups:
- id: imported.group.a
attributes:
- 1
- base: 1
requirement_level: recommended
brief: a fun group
note: a group note
stability: stable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ attribute_groups:
stability: stable
attributes:
- ref: local.group.attr
requirement_level: required
imports:
spans:
- imported.*
Expand Down
47 changes: 42 additions & 5 deletions crates/weaver_resolver/src/dependency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -710,14 +710,14 @@ impl ImportableDependency for V2Schema {
}
let mut attributes = vec![];
for ar in ag.attributes.iter() {
let attr = self.attribute_catalog.attribute(ar).ok_or(
let attr = self.attribute_catalog.attribute(&ar.base).ok_or(
Error::InvalidRegistryAttributeRef {
registry_name: self.schema_url.name().to_owned(),
attribute_ref: ar.0,
attribute_ref: ar.base.0,
},
)?;
attributes.push(attribute_catalog.attribute_ref_with_provenance(
convert_v2_attribute(attr, RequirementLevel::default(), None),
convert_v2_attribute(attr, ar.requirement_level.clone(), None),
AttributeSource::Dependency {
schema_url: self.schema_url.clone(),
},
Expand Down Expand Up @@ -1148,7 +1148,18 @@ mod tests {
attribute_groups: vec![
weaver_resolved_schema::v2::attribute_group::AttributeGroup {
id: "attribute_group.e".to_owned().into(),
attributes: vec![],
// A public group whose attribute ref carries a
// non-default requirement level; importing the group
// must preserve it.
attributes: vec![
weaver_resolved_schema::v2::attribute_group::AttributeGroupAttributeRef {
base: weaver_resolved_schema::v2::attribute::AttributeRef(0),
requirement_level:
weaver_semconv::attribute::RequirementLevel::Basic(
weaver_semconv::attribute::BasicRequirementLevelSpec::Required,
),
},
],
common: Default::default(),
provenance: Default::default(),
},
Expand Down Expand Up @@ -1193,7 +1204,15 @@ mod tests {
}],
attributes: vec![],
},
attribute_catalog: vec![],
attribute_catalog: vec![weaver_resolved_schema::v2::attribute::Attribute {
key: "attr.in.group".to_owned(),
r#type: weaver_semconv::attribute::AttributeType::PrimitiveOrArray(
weaver_semconv::attribute::PrimitiveOrArrayTypeSpec::String,
),
examples: None,
common: Default::default(),
provenance: Default::default(),
}],
refinements: weaver_resolved_schema::v2::refinements::Refinements {
spans: vec![],
metrics: vec![],
Expand Down Expand Up @@ -1324,6 +1343,24 @@ mod tests {
"Should import metric, event, entity, span and attribute_group"
);

// The imported public attribute group must preserve the per-attribute
// requirement level authored on its ref (rather than resetting it to
// the default).
let group = result
.iter()
.find(|g| g.id == "attribute_group.e")
.expect("attribute_group.e should be imported");
assert_eq!(group.attributes.len(), 1);
let attr = catalog
.attribute(&group.attributes[0])
.expect("imported attribute should exist in the catalog");
assert_eq!(
attr.requirement_level,
weaver_semconv::attribute::RequirementLevel::Basic(
weaver_semconv::attribute::BasicRequirementLevelSpec::Required,
)
);

Ok(())
}

Expand Down
Loading
Loading