Skip to content

Commit cd81001

Browse files
author
Bolyki György
committed
test(grafana): guard latest-position index predicate
1 parent 0465d31 commit cd81001

2 files changed

Lines changed: 103 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141

4242
#### Build, CI, internal
4343

44+
- test(grafana): guard latest-position dashboard queries against missing partial-index predicate (#5581 - @magrathean-uk)
4445
- build(nix): update mixFodDeps hash in nix builds ([954e8739](https://github.com/teslamate-org/teslamate/commit/954e8739326e092f7cddf7308dd4b704cc008f62) - @JakobLichterfeld)
4546
- build(deps): bump launch-editor from 2.13.2 to 2.14.1 in /website (#5426)
4647
- build(deps): update flake.lock (#5427)
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
defmodule TeslaMate.Grafana.DashboardQueriesTest do
2+
use ExUnit.Case, async: true
3+
4+
@dashboard_directory Path.expand("../../../grafana/dashboards", __DIR__)
5+
@query_keys ~w(definition query rawSql)
6+
7+
# A latest-position lookup needs the partial-index predicate introduced in
8+
# #5438. Match each positions block independently so a filter in another CTE
9+
# cannot hide an unindexed lookup.
10+
@latest_position_block ~r/from positions\b(?:(?!\bfrom\b).)*?order by date desc(?:(?!\bfrom\b).)*?limit 1\b/
11+
12+
test "latest position queries use complete position rows" do
13+
dashboards = Path.wildcard(Path.join(@dashboard_directory, "**/*.json"))
14+
15+
assert dashboards != []
16+
17+
offenders =
18+
dashboards
19+
|> Enum.flat_map(&dashboard_queries/1)
20+
|> Enum.flat_map(fn {path, query} ->
21+
query
22+
|> unfiltered_latest_position_blocks()
23+
|> Enum.map(&"#{path}: #{&1}")
24+
end)
25+
26+
assert offenders == []
27+
end
28+
29+
test "detector flags a positions block missing the filter even when it appears elsewhere" do
30+
query = """
31+
WITH metadata AS (SELECT 1 WHERE ideal_battery_range_km IS NOT NULL)
32+
SELECT date FROM positions WHERE car_id = 1 ORDER BY date DESC LIMIT 1
33+
"""
34+
35+
assert unfiltered_latest_position_blocks(query) != []
36+
end
37+
38+
test "detector accepts a positions block with the filter" do
39+
query =
40+
"SELECT date FROM positions WHERE car_id = 1 AND ideal_battery_range_km IS NOT NULL " <>
41+
"ORDER BY date DESC LIMIT 1"
42+
43+
assert unfiltered_latest_position_blocks(query) == []
44+
end
45+
46+
test "detector handles EXTRACT expressions in a positions block" do
47+
query = """
48+
SELECT EXTRACT(EPOCH FROM date)
49+
FROM positions
50+
WHERE car_id = 1
51+
ORDER BY date DESC
52+
LIMIT 1
53+
"""
54+
55+
assert unfiltered_latest_position_blocks(query) != []
56+
end
57+
58+
test "detector ignores positions queries with a larger limit" do
59+
query = "SELECT date FROM positions WHERE car_id = 1 ORDER BY date DESC LIMIT 10"
60+
61+
assert unfiltered_latest_position_blocks(query) == []
62+
end
63+
64+
defp dashboard_queries(path) do
65+
path
66+
|> File.read!()
67+
|> Jason.decode!()
68+
|> collect_queries()
69+
|> Enum.map(&{path, &1})
70+
end
71+
72+
defp collect_queries(%{} = value) do
73+
own_queries =
74+
value
75+
|> Map.take(@query_keys)
76+
|> Map.values()
77+
|> Enum.filter(&is_binary/1)
78+
79+
child_queries =
80+
value
81+
|> Map.values()
82+
|> Enum.flat_map(&collect_queries/1)
83+
84+
own_queries ++ child_queries
85+
end
86+
87+
defp collect_queries(values) when is_list(values), do: Enum.flat_map(values, &collect_queries/1)
88+
defp collect_queries(_value), do: []
89+
90+
defp unfiltered_latest_position_blocks(query) do
91+
normalized =
92+
query
93+
|> String.downcase()
94+
|> String.replace(~r/\s+/, " ")
95+
|> String.replace(~r/\bextract\s*\(\s*epoch\s+from\b/, "extract(epoch_from")
96+
97+
@latest_position_block
98+
|> Regex.scan(normalized)
99+
|> List.flatten()
100+
|> Enum.reject(&String.contains?(&1, "ideal_battery_range_km is not null"))
101+
end
102+
end

0 commit comments

Comments
 (0)