Complete source 6 engineering workflows and preserve release validation evidence
This commit is contained in:
@@ -669,6 +669,7 @@ fn exchanges(p: &Project, d: &Draft) -> Vec<Choice> {
|
||||
&& matches!(
|
||||
r.kind(),
|
||||
RelationKind::Interaction
|
||||
| RelationKind::CommunicationMean
|
||||
| RelationKind::FunctionalExchange
|
||||
| RelationKind::BoundaryExchange
|
||||
)
|
||||
@@ -677,7 +678,9 @@ fn exchanges(p: &Project, d: &Draft) -> Vec<Choice> {
|
||||
ScenarioViewpoint::Functional => r.kind() == RelationKind::FunctionalExchange,
|
||||
ScenarioViewpoint::ParticipantInteraction => matches!(
|
||||
r.kind(),
|
||||
RelationKind::Interaction | RelationKind::BoundaryExchange
|
||||
RelationKind::Interaction
|
||||
| RelationKind::CommunicationMean
|
||||
| RelationKind::BoundaryExchange
|
||||
),
|
||||
ScenarioViewpoint::BoundaryInterface => matches!(
|
||||
r.kind(),
|
||||
@@ -687,17 +690,37 @@ fn exchanges(p: &Project, d: &Draft) -> Vec<Choice> {
|
||||
}
|
||||
&& (!d.fixed_endpoints
|
||||
|| d.sender.zip(d.receiver).is_some_and(|(sender, receiver)| {
|
||||
validation::lifeline_matches_endpoint(p, sender, r.source()) == Some(true)
|
||||
&& validation::lifeline_matches_endpoint(p, receiver, r.target())
|
||||
== Some(true)
|
||||
let mut body = d.message_body();
|
||||
if let OccurrenceBody::Exchange { exchange, .. } = &mut body {
|
||||
*exchange = Some(r.id);
|
||||
}
|
||||
validation::message_endpoints(p, &body).is_some_and(|(source, target)| {
|
||||
validation::lifeline_matches_endpoint(p, sender, source) == Some(true)
|
||||
&& validation::lifeline_matches_endpoint(p, receiver, target)
|
||||
== Some(true)
|
||||
})
|
||||
}))
|
||||
})
|
||||
.map(|r| Choice {
|
||||
id: r.id,
|
||||
label: format!(
|
||||
"{} · {} → {}",
|
||||
"{} · {} {} {}",
|
||||
r.name,
|
||||
p.named(r.source()),
|
||||
if matches!(
|
||||
r.body,
|
||||
RelationBody::CommunicationMean {
|
||||
oriented: false,
|
||||
..
|
||||
} | RelationBody::BoundaryExchange {
|
||||
oriented: false,
|
||||
..
|
||||
}
|
||||
) {
|
||||
"↔"
|
||||
} else {
|
||||
"→"
|
||||
},
|
||||
p.named(r.target())
|
||||
),
|
||||
})
|
||||
@@ -837,6 +860,38 @@ impl Draft {
|
||||
lines(p, self.owner)
|
||||
.into_iter()
|
||||
.filter(|line| {
|
||||
if self
|
||||
.exchange
|
||||
.and_then(|id| p.relations.get(&id))
|
||||
.is_some_and(|r| {
|
||||
matches!(
|
||||
r.body,
|
||||
RelationBody::CommunicationMean { .. }
|
||||
| RelationBody::BoundaryExchange {
|
||||
oriented: false,
|
||||
..
|
||||
}
|
||||
)
|
||||
})
|
||||
{
|
||||
let mut body = self.message_body();
|
||||
if let OccurrenceBody::Exchange {
|
||||
sender: from,
|
||||
receiver: to,
|
||||
..
|
||||
} = &mut body
|
||||
{
|
||||
if sender {
|
||||
*from = Some(line.id);
|
||||
} else {
|
||||
*to = Some(line.id);
|
||||
}
|
||||
}
|
||||
return !matches!(
|
||||
validation::resolve_message_endpoints(p, &body),
|
||||
validation::MessageEndpointResolution::Conflict
|
||||
);
|
||||
}
|
||||
endpoint.is_none_or(|endpoint| {
|
||||
validation::lifeline_matches_endpoint(p, line.id, endpoint) == Some(true)
|
||||
})
|
||||
@@ -2315,6 +2370,81 @@ pub(crate) mod direct_tests {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn reverse_communication_canvas_choices_and_correlated_reply_share_core_direction() {
|
||||
let mut p = Project::empty();
|
||||
let a = p
|
||||
.add_element(Kind::Entity, Layer::Oa, "Crew", None)
|
||||
.unwrap();
|
||||
let b = p
|
||||
.add_element(Kind::Actor, Layer::Oa, "Station", None)
|
||||
.unwrap();
|
||||
let exchange = p.relate(RelationKind::CommunicationMean, "Radio", a, b);
|
||||
let owner = scenario(&mut p, "Participant dialogue");
|
||||
if let Some(SharedBody::Scenario { viewpoint, .. }) =
|
||||
p.elements.get_mut(&owner).unwrap().shared_mut()
|
||||
{
|
||||
*viewpoint = ScenarioViewpoint::ParticipantInteraction;
|
||||
}
|
||||
let la = lifeline(&mut p, owner, a);
|
||||
let lb = lifeline(&mut p, owner, b);
|
||||
let mut state = State::default();
|
||||
state
|
||||
.begin_message(
|
||||
&p,
|
||||
direct_tests::context(owner),
|
||||
Slot {
|
||||
parent: owner,
|
||||
before: None,
|
||||
},
|
||||
lb,
|
||||
la,
|
||||
MessageStyle::CallReply,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
exchanges(&p, state.draft.as_ref().unwrap())
|
||||
.iter()
|
||||
.any(|c| c.id == exchange)
|
||||
);
|
||||
state
|
||||
.update(&p, Event::Exchange(named_choice(&p, exchange)))
|
||||
.unwrap();
|
||||
let mut document =
|
||||
crate::document::Document::from_project(p.clone(), Default::default()).unwrap();
|
||||
let original = document.bundle().clone();
|
||||
document
|
||||
.execute(state.prepare_pending(&p).unwrap())
|
||||
.unwrap();
|
||||
let accepted = document.project();
|
||||
let steps = accepted.elements[&owner].steps().unwrap();
|
||||
assert_eq!(steps.len(), 2);
|
||||
for (id, expected) in [(steps[0], (b, a)), (steps[1], (a, b))] {
|
||||
let Some(SharedBody::Occurrence { body, .. }) = accepted.elements[&id].shared() else {
|
||||
unreachable!()
|
||||
};
|
||||
assert_eq!(
|
||||
validation::message_endpoints(accepted, body),
|
||||
Some(expected)
|
||||
);
|
||||
}
|
||||
let after = document.bundle().clone();
|
||||
assert!(document.undo());
|
||||
assert_eq!(document.bundle(), &original);
|
||||
assert!(document.redo());
|
||||
assert_eq!(document.bundle(), &after);
|
||||
let mut directed = p.clone();
|
||||
if let RelationBody::CommunicationMean { oriented, .. } =
|
||||
&mut directed.relations.get_mut(&exchange).unwrap().body
|
||||
{
|
||||
*oriented = true;
|
||||
}
|
||||
assert!(
|
||||
!exchanges(&directed, state.draft.as_ref().unwrap())
|
||||
.iter()
|
||||
.any(|c| c.id == exchange)
|
||||
);
|
||||
}
|
||||
fn scenario(p: &mut Project, name: &str) -> Id {
|
||||
let mut e = p
|
||||
.make_element(Kind::Scenario, Layer::Oa, name, None)
|
||||
|
||||
@@ -1538,6 +1538,7 @@ mod tests {
|
||||
projection: ProjectionKind::StateMachine { machine },
|
||||
nodes: BTreeMap::new(),
|
||||
routes: vec![],
|
||||
view_options: Default::default(),
|
||||
},
|
||||
);
|
||||
for (element, x, y, width, height) in [
|
||||
|
||||
@@ -293,6 +293,7 @@ pub fn create_view(project: &Project, machine: Id) -> Result<Plan, ModelError> {
|
||||
projection: ProjectionKind::StateMachine { machine },
|
||||
nodes: BTreeMap::new(),
|
||||
routes: vec![],
|
||||
view_options: Default::default(),
|
||||
},
|
||||
);
|
||||
let members: Vec<_> = project
|
||||
|
||||
@@ -41,6 +41,7 @@ pub fn popup(app: &App, opened: &Opened, clean: bool) -> Option<Menu<Message>> {
|
||||
if let Some(context) = context.filter(|context| context.diagram == id) {
|
||||
entries.extend([
|
||||
item("View properties", Message::EditProperties(id), true),
|
||||
item("Duplicate view", Message::DuplicateDiagram(id), true),
|
||||
item("Fit diagram", diagram_event(Event::Fit(context)), true),
|
||||
item(
|
||||
"Create another state view",
|
||||
|
||||
Reference in New Issue
Block a user