Skip to content

Commit 58d4665

Browse files
committed
graph, graphql, tests: Support specifying sort order in _logs queries
1 parent e54e04d commit 58d4665

File tree

6 files changed

+108
-3
lines changed

6 files changed

+108
-3
lines changed

graph/src/components/log_store/elasticsearch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ impl ElasticsearchLogStore {
8484
"from": query.skip,
8585
"size": query.first,
8686
"sort": [
87-
{ "timestamp": { "order": "desc" } }
87+
{ "timestamp": { "order": query.order_direction.as_str() } }
8888
]
8989
})
9090
}

graph/src/components/log_store/loki.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,16 @@ impl LokiLogStore {
5555
from: &str,
5656
to: &str,
5757
limit: u32,
58+
order_direction: super::OrderDirection,
5859
) -> Result<Vec<LogEntry>, LogStoreError> {
5960
let url = format!("{}/loki/api/v1/query_range", self.endpoint);
6061

62+
// Map order direction to Loki's direction parameter
63+
let direction = match order_direction {
64+
super::OrderDirection::Desc => "backward", // Most recent first
65+
super::OrderDirection::Asc => "forward", // Oldest first
66+
};
67+
6168
let mut request = self
6269
.client
6370
.get(&url)
@@ -66,7 +73,7 @@ impl LokiLogStore {
6673
("start", from),
6774
("end", to),
6875
("limit", &limit.to_string()),
69-
("direction", "backward"), // Most recent first
76+
("direction", direction),
7077
])
7178
.timeout(Duration::from_secs(10));
7279

@@ -158,7 +165,9 @@ impl LogStore for LokiLogStore {
158165
// Execute query with limit + skip to handle pagination
159166
let limit = query.first + query.skip;
160167

161-
let mut entries = self.execute_query(&logql_query, from, to, limit).await?;
168+
let mut entries = self
169+
.execute_query(&logql_query, from, to, limit, query.order_direction)
170+
.await?;
162171

163172
// Apply skip/first pagination
164173
if query.skip > 0 {
@@ -239,6 +248,7 @@ mod tests {
239248
search: None,
240249
first: 100,
241250
skip: 0,
251+
order_direction: crate::components::log_store::OrderDirection::Desc,
242252
};
243253

244254
let logql = store.build_logql_query(&query);
@@ -256,6 +266,7 @@ mod tests {
256266
search: None,
257267
first: 100,
258268
skip: 0,
269+
order_direction: crate::components::log_store::OrderDirection::Desc,
259270
};
260271

261272
let logql = store.build_logql_query(&query);
@@ -273,6 +284,7 @@ mod tests {
273284
search: Some("transaction failed".to_string()),
274285
first: 100,
275286
skip: 0,
287+
order_direction: crate::components::log_store::OrderDirection::Desc,
276288
};
277289

278290
let logql = store.build_logql_query(&query);

graph/src/schema/api.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,6 +1371,18 @@ fn logs_field() -> s::Field {
13711371
default_value: Some(s::Value::Int(0.into())),
13721372
directives: vec![],
13731373
},
1374+
// orderDirection: OrderDirection (default desc)
1375+
s::InputValue {
1376+
position: Pos::default(),
1377+
description: Some(
1378+
"Sort direction for results. Default: desc (newest first)."
1379+
.to_string()
1380+
),
1381+
name: String::from("orderDirection"),
1382+
value_type: s::Type::NamedType(String::from("OrderDirection")),
1383+
default_value: Some(s::Value::Enum(String::from("desc"))),
1384+
directives: vec![],
1385+
},
13741386
],
13751387
field_type: s::Type::NonNullType(Box::new(s::Type::ListType(Box::new(
13761388
s::Type::NonNullType(Box::new(s::Type::NamedType(String::from("_Log_")))),

graph/src/schema/logs.graphql

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ enum LogLevel {
1515
DEBUG
1616
}
1717

18+
"""
19+
Sort direction for query results.
20+
"""
21+
enum OrderDirection {
22+
"Ascending order (oldest first for timestamps)"
23+
asc
24+
"Descending order (newest first for timestamps)"
25+
desc
26+
}
27+
1828
"""
1929
A log entry emitted by a subgraph during indexing.
2030
Logs can be generated by the subgraph's AssemblyScript code using the `log.*` functions.

graphql/src/store/logs.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ pub fn build_log_query(
6363
let mut search = None;
6464
let mut first = 100;
6565
let mut skip = 0;
66+
let mut order_direction = graph::components::log_store::OrderDirection::Desc;
6667

6768
// Parse arguments
6869
for (name, value) in &field.arguments {
@@ -156,6 +157,17 @@ pub fn build_log_query(
156157
skip = skip_u32;
157158
}
158159
}
160+
"orderDirection" => {
161+
if let r::Value::Enum(order_str) = value {
162+
order_direction = order_str.parse().map_err(|e: String| {
163+
QueryExecutionError::InvalidArgumentError(
164+
field.position,
165+
"orderDirection".to_string(),
166+
q::Value::String(e),
167+
)
168+
})?;
169+
}
170+
}
159171
_ => {
160172
// Unknown argument, ignore
161173
}
@@ -170,5 +182,6 @@ pub fn build_log_query(
170182
search,
171183
first,
172184
skip,
185+
order_direction,
173186
})
174187
}

tests/tests/integration_tests.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,6 +1578,64 @@ async fn test_logs_query(ctx: TestContext) -> anyhow::Result<()> {
15781578
println!("✓ Field selection works correctly - only requested fields returned");
15791579
}
15801580

1581+
// Test 8: Order direction - ascending
1582+
let query = r#"{
1583+
_logs(first: 10, orderDirection: asc) {
1584+
id
1585+
timestamp
1586+
}
1587+
}"#
1588+
.to_string();
1589+
let resp = subgraph.query(&query).await?;
1590+
1591+
let logs = resp["data"]["_logs"]
1592+
.as_array()
1593+
.context("Expected _logs to be an array")?;
1594+
1595+
// Verify ascending order (each timestamp >= previous)
1596+
if logs.len() > 1 {
1597+
for i in 1..logs.len() {
1598+
let prev_ts = logs[i - 1]["timestamp"].as_str().unwrap();
1599+
let curr_ts = logs[i]["timestamp"].as_str().unwrap();
1600+
assert!(
1601+
curr_ts >= prev_ts,
1602+
"Expected ascending order, but {} came before {}",
1603+
prev_ts,
1604+
curr_ts
1605+
);
1606+
}
1607+
println!("✓ Ascending order works correctly");
1608+
}
1609+
1610+
// Test 9: Order direction - descending (explicit)
1611+
let query = r#"{
1612+
_logs(first: 10, orderDirection: desc) {
1613+
id
1614+
timestamp
1615+
}
1616+
}"#
1617+
.to_string();
1618+
let resp = subgraph.query(&query).await?;
1619+
1620+
let logs = resp["data"]["_logs"]
1621+
.as_array()
1622+
.context("Expected _logs to be an array")?;
1623+
1624+
// Verify descending order (each timestamp <= previous)
1625+
if logs.len() > 1 {
1626+
for i in 1..logs.len() {
1627+
let prev_ts = logs[i - 1]["timestamp"].as_str().unwrap();
1628+
let curr_ts = logs[i]["timestamp"].as_str().unwrap();
1629+
assert!(
1630+
curr_ts <= prev_ts,
1631+
"Expected descending order, but {} came before {}",
1632+
prev_ts,
1633+
curr_ts
1634+
);
1635+
}
1636+
println!("✓ Descending order works correctly");
1637+
}
1638+
15811639
Ok(())
15821640
}
15831641

0 commit comments

Comments
 (0)