mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-09-09 19:31:57 +02:00
fix(import): treat a pair's row ID as identity, not content
Headers, URL parameters, and environment variables each carry an `id` that identifies the row to the pair editor. Importers leave it empty and the editor stamps one in the first time it touches a resource, so merely opening an imported request made the next import report it as locally edited. `comparable` now drops `id` at every level rather than only the top, since a row ID says no more about content than the model's own ID. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6796569466
commit
64b9479deb
@@ -1005,17 +1005,33 @@ enum KeyStatus<'a> {
|
|||||||
/// The deprecated environment `base` flag mirrors `parentModel`, which is compared already.
|
/// The deprecated environment `base` flag mirrors `parentModel`, which is compared already.
|
||||||
/// Importers number `sortPriority` from source order, so comparing it would turn one insertion
|
/// Importers number `sortPriority` from source order, so comparing it would turn one insertion
|
||||||
/// into an update of everything after it.
|
/// into an update of everything after it.
|
||||||
fn comparable(mut value: Value) -> Value {
|
fn comparable(value: Value) -> Value {
|
||||||
|
let mut value = strip_ids(value);
|
||||||
if let Some(object) = value.as_object_mut() {
|
if let Some(object) = value.as_object_mut() {
|
||||||
for field in
|
for field in ["model", "workspaceId", "createdAt", "updatedAt", "base", "sortPriority"] {
|
||||||
["id", "model", "workspaceId", "createdAt", "updatedAt", "base", "sortPriority"]
|
|
||||||
{
|
|
||||||
object.remove(field);
|
object.remove(field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A header, parameter, or variable carries an `id` that identifies its row to the editor rather
|
||||||
|
/// than anything about its content, and the editor fills those in the first time it touches a
|
||||||
|
/// resource. Dropping every `id` keeps that from reading as a local edit.
|
||||||
|
fn strip_ids(value: Value) -> Value {
|
||||||
|
match value {
|
||||||
|
Value::Object(object) => Value::Object(
|
||||||
|
object
|
||||||
|
.into_iter()
|
||||||
|
.filter(|(key, _)| key != "id")
|
||||||
|
.map(|(key, value)| (key, strip_ids(value)))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
Value::Array(items) => Value::Array(items.into_iter().map(strip_ids).collect()),
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const CONTENT_HASH_VERSION: &str = "v1:";
|
const CONTENT_HASH_VERSION: &str = "v1:";
|
||||||
|
|
||||||
/// Identifies a resource's content well enough to tell "changed since the last import" from
|
/// Identifies a resource's content well enough to tell "changed since the last import" from
|
||||||
@@ -1920,11 +1936,18 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn first_import(query_manager: &QueryManager) -> BatchUpsertResult {
|
fn first_import(query_manager: &QueryManager) -> BatchUpsertResult {
|
||||||
|
first_import_with(query_manager, imported_resources())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn first_import_with(
|
||||||
|
query_manager: &QueryManager,
|
||||||
|
resources: ImportResources,
|
||||||
|
) -> BatchUpsertResult {
|
||||||
let plan = plan_import_resources(
|
let plan = plan_import_resources(
|
||||||
query_manager,
|
query_manager,
|
||||||
"OpenAPI".to_string(),
|
"OpenAPI".to_string(),
|
||||||
ImportDestination::NewWorkspace,
|
ImportDestination::NewWorkspace,
|
||||||
imported_resources(),
|
resources,
|
||||||
Some(importer_keys()),
|
Some(importer_keys()),
|
||||||
Some(linked_origin()),
|
Some(linked_origin()),
|
||||||
)
|
)
|
||||||
@@ -2563,6 +2586,49 @@ mod tests {
|
|||||||
assert_eq!(root.resolution, Some(ImportConflictResolution::KeepMine));
|
assert_eq!(root.resolution, Some(ImportConflictResolution::KeepMine));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filling_in_editor_row_ids_is_not_an_edit() {
|
||||||
|
let (query_manager, _blob_manager, _rx) =
|
||||||
|
yaak_models::init_in_memory().expect("initialize database");
|
||||||
|
let mut resources = imported_resources();
|
||||||
|
resources.http_requests[0].headers = vec![HttpRequestHeader {
|
||||||
|
enabled: true,
|
||||||
|
name: "Accept".to_string(),
|
||||||
|
value: "application/json".to_string(),
|
||||||
|
id: None,
|
||||||
|
}];
|
||||||
|
let committed = first_import_with(&query_manager, resources.clone());
|
||||||
|
let workspace_id = committed.workspaces[0].id.clone();
|
||||||
|
let root_id = committed
|
||||||
|
.http_requests
|
||||||
|
.iter()
|
||||||
|
.find(|r| r.name == "Root Request")
|
||||||
|
.expect("root request")
|
||||||
|
.id
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
// Opening the request in the editor stamps a row ID onto every header it renders.
|
||||||
|
{
|
||||||
|
let db = query_manager.connect();
|
||||||
|
let root = db.get_http_request(&root_id).expect("get root");
|
||||||
|
let headers = root
|
||||||
|
.headers
|
||||||
|
.iter()
|
||||||
|
.map(|h| HttpRequestHeader { id: Some("hd_generated".to_string()), ..h.clone() })
|
||||||
|
.collect();
|
||||||
|
db.upsert_http_request(&HttpRequest { headers, ..root }, &UpdateSource::Background)
|
||||||
|
.expect("stamp row ids");
|
||||||
|
}
|
||||||
|
|
||||||
|
let plan = replan(&query_manager, &workspace_id, resources);
|
||||||
|
assert_eq!(
|
||||||
|
item_by_name(&plan, "Root Request").action,
|
||||||
|
ImportPlanAction::Unchanged,
|
||||||
|
"a row ID is not content: {:?}",
|
||||||
|
item_by_name(&plan, "Root Request").changed_fields,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reordering_the_source_document_changes_nothing() {
|
fn reordering_the_source_document_changes_nothing() {
|
||||||
let (query_manager, _blob_manager, _rx) =
|
let (query_manager, _blob_manager, _rx) =
|
||||||
|
|||||||
Reference in New Issue
Block a user