Skip to content

Commit a7eb05a

Browse files
committed
refactor: extract shared truncate() helper, replace six inline _truncate() implementations
1 parent 5e0d007 commit a7eb05a

8 files changed

Lines changed: 63 additions & 38 deletions

File tree

app/integrations/azure_sql.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from pydantic import Field, field_validator
2121

2222
from app.strict_config import StrictConfigModel
23+
from app.utils.truncation import truncate
2324

2425
logger = logging.getLogger(__name__)
2526

@@ -174,12 +175,6 @@ def _get_connection(config: AzureSQLConfig) -> Any:
174175
return pyodbc.connect(conn_str, timeout=int(config.timeout_seconds))
175176

176177

177-
def _truncate(text: str, max_len: int = _QUERY_TRUNCATE_LEN) -> str:
178-
if len(text) <= max_len:
179-
return text
180-
return text[:max_len] + "..."
181-
182-
183178
def validate_azure_sql_config(config: AzureSQLConfig) -> AzureSQLValidationResult:
184179
"""Validate Azure SQL connectivity with a lightweight query."""
185180
if not config.server:
@@ -397,7 +392,7 @@ def get_current_queries(
397392
"cpu_time_ms": row[9] or 0,
398393
"logical_reads": row[10] or 0,
399394
"writes": row[11] or 0,
400-
"query_text": _truncate(row[12] or ""),
395+
"query_text": truncate(row[12] or "", _QUERY_TRUNCATE_LEN),
401396
}
402397
)
403398

@@ -544,7 +539,7 @@ def get_slow_queries(
544539
queries.append(
545540
{
546541
"query_hash": str(row[0]) if row[0] else "",
547-
"query_text": _truncate(row[1] or ""),
542+
"query_text": truncate(row[1] or "", _QUERY_TRUNCATE_LEN),
548543
"execution_count": row[2] or 0,
549544
"total_time_ms": round(float(row[3] or 0), 3),
550545
"avg_time_ms": round(float(row[4] or 0), 3),

app/integrations/mariadb.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from app.strict_config import StrictConfigModel
1818
from app.utils.coercion import safe_int
19+
from app.utils.truncation import truncate
1920

2021
logger = logging.getLogger(__name__)
2122

@@ -149,12 +150,6 @@ def validate_mariadb_config(config: MariaDBConfig) -> MariaDBValidationResult:
149150
return MariaDBValidationResult(ok=False, detail=f"MariaDB connection failed: {err}")
150151

151152

152-
def _truncate(text: str, max_len: int = _QUERY_TRUNCATE_LEN) -> str:
153-
if len(text) <= max_len:
154-
return text
155-
return text[:max_len] + "..."
156-
157-
158153
def mariadb_is_available(sources: dict[str, dict]) -> bool:
159154
"""Check if MariaDB integration credentials are present."""
160155
mdb = sources.get("mariadb", {})
@@ -213,7 +208,7 @@ def get_process_list(
213208
"command": row[4],
214209
"time_secs": row[5] or 0,
215210
"state": row[6] or "",
216-
"query": _truncate(row[7] or ""),
211+
"query": truncate(row[7] or "", _QUERY_TRUNCATE_LEN),
217212
}
218213
)
219214
return {
@@ -358,7 +353,7 @@ def get_slow_queries(
358353
for row in cur.fetchall():
359354
queries.append(
360355
{
361-
"digest_text": _truncate(row[0] or ""),
356+
"digest_text": truncate(row[0] or "", _QUERY_TRUNCATE_LEN),
362357
"count": row[1] or 0,
363358
"avg_time_ms": float(row[2]) if row[2] is not None else 0,
364359
"total_time_ms": float(row[3]) if row[3] is not None else 0,

app/integrations/mysql.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from pydantic import Field, field_validator
1515

1616
from app.strict_config import StrictConfigModel
17+
from app.utils.truncation import truncate
1718

1819
DEFAULT_MYSQL_PORT = 3306
1920
DEFAULT_MYSQL_USER = "root"
@@ -200,12 +201,6 @@ def validate_mysql_config(config: MySQLConfig) -> MySQLValidationResult:
200201
return MySQLValidationResult(ok=False, detail=f"MySQL connection failed: {err}")
201202

202203

203-
def _truncate(text: str, max_len: int = _QUERY_TRUNCATE_LEN) -> str:
204-
if len(text) <= max_len:
205-
return text
206-
return text[:max_len] + "..."
207-
208-
209204
def mysql_is_available(sources: dict[str, dict]) -> bool:
210205
"""Check if MySQL integration identifying params are present."""
211206
my = sources.get("mysql", {})
@@ -359,7 +354,7 @@ def get_current_processes(
359354
"command": row["COMMAND"],
360355
"time_seconds": row["TIME"] or 0,
361356
"state": row["STATE"] or "",
362-
"query": _truncate(row["INFO"] or ""),
357+
"query": truncate(row["INFO"] or "", _QUERY_TRUNCATE_LEN),
363358
}
364359
)
365360

@@ -511,7 +506,7 @@ def get_slow_queries(
511506
for row in cur.fetchall():
512507
queries.append(
513508
{
514-
"digest_text": _truncate(row["DIGEST_TEXT"] or ""),
509+
"digest_text": truncate(row["DIGEST_TEXT"] or "", _QUERY_TRUNCATE_LEN),
515510
"schema_name": row["SCHEMA_NAME"] or "",
516511
"count": row["COUNT_STAR"] or 0,
517512
"avg_time_ms": float(row["avg_time_ms"])

app/nodes/publish_findings/gitlab_writeback.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55

66
from app.integrations.gitlab import build_gitlab_config, post_gitlab_mr_note
77
from app.state import InvestigationState
8+
from app.utils.truncation import truncate
89

910
logger = logging.getLogger(__name__)
1011

1112

13+
_GITLAB_MR_NOTE_LIMIT = 4000
14+
15+
1216
def _build_mr_note(report: str) -> str:
13-
body = report.strip()
14-
if len(body) > 4000:
15-
body = body[:3997] + "..."
17+
body = truncate(report.strip(), _GITLAB_MR_NOTE_LIMIT)
1618
return f"### RCA Finding\n\n<details>\n<summary>Investigation summary</summary>\n\n{body}\n\n</details>"
1719

1820

app/utils/discord_delivery.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import Any
88

99
from app.utils.delivery_transport import post_json
10+
from app.utils.truncation import truncate
1011

1112
logger = logging.getLogger(__name__)
1213

@@ -80,18 +81,14 @@ def create_discord_thread(
8081
_EMBED_DESCRIPTION_LIMIT = 4096
8182

8283

83-
def _truncate(text: str, limit: int) -> str:
84-
return (text[: limit - 1] + "…") if len(text) > limit else text
85-
86-
8784
def send_discord_report(report: str, discord_ctx: dict[str, Any]) -> tuple[bool, str]:
8885
channel_id: str = str(discord_ctx.get("channel_id") or "")
8986
thread_id: str = str(discord_ctx.get("thread_id") or "")
9087
bot_token: str = str(discord_ctx.get("bot_token") or "")
9188
embed = {
92-
"title": _truncate("Investigation Complete", _EMBED_TITLE_LIMIT),
89+
"title": truncate("Investigation Complete", _EMBED_TITLE_LIMIT, ellipsis="…"),
9390
"color": 15158332,
94-
"description": _truncate(report, _EMBED_DESCRIPTION_LIMIT),
91+
"description": truncate(report, _EMBED_DESCRIPTION_LIMIT, ellipsis="…"),
9592
"footer": {"text": "OpenSRE Investigation"},
9693
}
9794
target = thread_id if thread_id else channel_id

app/utils/telegram_delivery.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import Any
99

1010
from app.utils.delivery_transport import post_json
11+
from app.utils.truncation import truncate
1112

1213
logger = logging.getLogger(__name__)
1314

@@ -44,10 +45,6 @@ def _install_httpx_token_filter() -> None:
4445
_install_httpx_token_filter()
4546

4647

47-
def _truncate(text: str, limit: int) -> str:
48-
return (text[: limit - 1] + "…") if len(text) > limit else text
49-
50-
5148
def _redact_token(text: str, bot_token: str) -> str:
5249
"""Replace bot token with <redacted> to prevent accidental log/error leakage."""
5350
if bot_token and bot_token in text:
@@ -103,7 +100,7 @@ def send_telegram_report(report: str, telegram_ctx: dict[str, Any]) -> tuple[boo
103100
if not bot_token or not chat_id:
104101
return False, "Missing bot_token or chat_id"
105102
reply_to_message_id: str = str(telegram_ctx.get("reply_to_message_id") or "")
106-
text = _truncate(report, _MESSAGE_LIMIT)
103+
text = truncate(report, _MESSAGE_LIMIT, ellipsis="…")
107104
post_success, error, _ = post_telegram_message(
108105
chat_id, text, bot_token, reply_to_message_id=reply_to_message_id
109106
)

app/utils/truncation.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""Shared text truncation utility."""
2+
3+
4+
def truncate(text: str, limit: int, ellipsis: str = "...") -> str:
5+
if len(text) <= limit:
6+
return text
7+
return text[: limit - len(ellipsis)] + ellipsis

tests/utils/test_truncation.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Tests for the shared truncation utility."""
2+
3+
import pytest
4+
5+
from app.utils.truncation import truncate
6+
7+
8+
@pytest.mark.parametrize(
9+
"text,limit,ellipsis,expected",
10+
[
11+
("short", 10, "...", "short"),
12+
("exactly10c", 10, "...", "exactly10c"),
13+
("this is a long string", 10, "...", "this is..."),
14+
("long", 3, "...", "..."),
15+
("unicode", 5, "…", "unic…"),
16+
("no change needed", 100, "...", "no change needed"),
17+
],
18+
)
19+
def test_truncate(text: str, limit: int, ellipsis: str, expected: str) -> None:
20+
assert truncate(text, limit, ellipsis=ellipsis) == expected
21+
22+
23+
def test_truncate_default_ellipsis() -> None:
24+
result = truncate("hello world", 8)
25+
assert result == "hello..."
26+
assert len(result) == 8
27+
28+
29+
def test_truncate_unicode_ellipsis() -> None:
30+
result = truncate("hello world", 8, ellipsis="…")
31+
assert result.endswith("…")
32+
assert len(result) == 8
33+
34+
35+
def test_truncate_text_at_exact_limit_not_truncated() -> None:
36+
text = "a" * 10
37+
assert truncate(text, 10) == text

0 commit comments

Comments
 (0)