Skip to content

Commit 0299232

Browse files
azproductionMikhail Davydov
andauthored
fix(router): include persisted document hash in usage reports (#1377)
Co-authored-by: Mikhail Davydov <davydov@devlab.co>
1 parent f46dbd8 commit 0299232

5 files changed

Lines changed: 107 additions & 1 deletion

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
hive-router: patch
3+
---
4+
5+
# Report `persistedDocumentHash` in usage reports
6+
7+
Usage reports now include the resolved persisted document id, so Hive Console can match
8+
requests to app deployments and populate their "Last used" data. Previously the router
9+
resolved the document id but always omitted it from the usage report.
10+
11+
Closes https://github.com/graphql-hive/router/issues/1343

bin/router/src/pipeline/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ pub async fn graphql_request_handler(
509509
hive_usage_agent,
510510
shared_response.error_count(),
511511
Some(usage_reporting::request_details_from_ntex_request(req)),
512+
prepared_operation.resolved_document_id.as_deref(),
512513
)
513514
.await;
514515
}

bin/router/src/pipeline/usage_reporting.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ pub async fn collect_usage_report<'a>(
118118
hive_usage_agent: &UsageAgent,
119119
error_count: usize,
120120
request_details: Option<RequestDetails>,
121+
persisted_document_hash: Option<&str>,
121122
) {
122123
let timestamp = SystemTime::now()
123124
.duration_since(UNIX_EPOCH)
@@ -138,7 +139,7 @@ pub async fn collect_usage_report<'a>(
138139
OperationKind::Subscription => OperationType::Subscription,
139140
}),
140141
operation_name: operation_name.map(|s| s.to_owned()),
141-
persisted_document_hash: None,
142+
persisted_document_hash: persisted_document_hash.map(|hash| hash.to_owned()),
142143
};
143144

144145
if let Err(err) = hive_usage_agent

bin/router/src/pipeline/websocket_server.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,9 @@ async fn handle_text_frame(
633633
hive_usage_agent,
634634
shared_response.error_count(),
635635
Some(request_details),
636+
// The graphql-transport-ws Subscribe payload carries no document id
637+
// and this path never invokes the document id resolver.
638+
None,
636639
)
637640
.await;
638641
}

e2e/src/telemetry/usage_reporting.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,3 +892,93 @@ async fn usage_reporting_exclude_runs_before_at_least_once() {
892892
let total_ops = mock.total_operations_count().await;
893893
assert_eq!(total_ops, 2);
894894
}
895+
896+
/// Test that an operation resolved from a persisted document reports the resolved
897+
/// document id as `persistedDocumentHash`, while a plain query reports none.
898+
/// https://github.com/graphql-hive/router/issues/1343
899+
#[ntex::test]
900+
async fn usage_reporting_includes_persisted_document_hash() {
901+
let supergraph_path =
902+
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("supergraph.graphql");
903+
let supergraph_path = supergraph_path.to_str().unwrap();
904+
905+
let mock = MockUsageEndpoint::start();
906+
let usage_endpoint = &mock.address;
907+
908+
let doc_id = "app~1.0.0~sha256:usage123";
909+
let manifest = tempfile::NamedTempFile::new().expect("failed to create manifest file");
910+
std::fs::write(
911+
manifest.path(),
912+
sonic_rs::to_string(&sonic_rs::json!({ doc_id: "{ users { id } }" }))
913+
.expect("failed to serialize manifest"),
914+
)
915+
.expect("failed to write manifest");
916+
917+
let subgraphs = TestSubgraphs::builder().build().start().await;
918+
919+
let router = TestRouter::builder()
920+
.inline_config(format!(
921+
r#"
922+
supergraph:
923+
source: file
924+
path: {supergraph_path}
925+
926+
persisted_documents:
927+
enabled: true
928+
storage:
929+
type: file
930+
path: "{manifest_path}"
931+
932+
telemetry:
933+
hive:
934+
token: test-token
935+
usage_reporting:
936+
enabled: true
937+
endpoint: {usage_endpoint}
938+
buffer_size: 1
939+
flush_interval: 100ms
940+
"#,
941+
manifest_path = manifest.path().display(),
942+
))
943+
.with_subgraphs(&subgraphs)
944+
.build()
945+
.start()
946+
.await;
947+
948+
// Resolved from the persisted document manifest.
949+
let res = router
950+
.send_post_request("/graphql", sonic_rs::json!({ "documentId": doc_id }), None)
951+
.await;
952+
assert!(res.status().is_success());
953+
954+
mock.wait_for_reports(1).await;
955+
let reports = mock.reports().await;
956+
let operations = reports[0]["operations"]
957+
.as_array()
958+
.expect("report should contain operations");
959+
assert_eq!(operations.len(), 1);
960+
assert_eq!(
961+
operations[0]["persistedDocumentHash"].as_str(),
962+
Some(doc_id),
963+
"operation should carry the resolved persisted document id: {}",
964+
reports[0]
965+
);
966+
967+
// A plain query (allowed since require_id is off) must not carry a hash.
968+
let res = router
969+
.send_graphql_request("{ users { id } }", None, None)
970+
.await;
971+
assert!(res.status().is_success());
972+
973+
mock.wait_for_reports(2).await;
974+
let reports = mock.reports().await;
975+
let operations = reports[1]["operations"]
976+
.as_array()
977+
.expect("report should contain operations");
978+
assert_eq!(operations.len(), 1);
979+
assert!(
980+
operations[0]["persistedDocumentHash"].is_null(),
981+
"plain query must not report a persisted document hash: {}",
982+
reports[1]
983+
);
984+
}

0 commit comments

Comments
 (0)