Compare commits
No commits in common. "d2f761877205c69559ce7cf65d0aa440eeb0e19e" and "a0d6b1e0d4b4cc2e307588a7ce29eb496502d00e" have entirely different histories.
d2f7618772
...
a0d6b1e0d4
@ -1 +1 @@
|
|||||||
v20260113-06-overrides-error-match-modes
|
v20260113-05-reporter-menu-restrict
|
||||||
|
|||||||
@ -75,16 +75,7 @@ def overrides():
|
|||||||
if ov.match_status:
|
if ov.match_status:
|
||||||
crit.append(f"status == {ov.match_status}")
|
crit.append(f"status == {ov.match_status}")
|
||||||
if ov.match_error_contains:
|
if ov.match_error_contains:
|
||||||
mode = (getattr(ov, "match_error_mode", None) or "contains").strip().lower()
|
crit.append(f"error contains '{ov.match_error_contains}'")
|
||||||
if mode == "exact":
|
|
||||||
label = "error exact"
|
|
||||||
elif mode == "starts_with":
|
|
||||||
label = "error starts with"
|
|
||||||
elif mode == "ends_with":
|
|
||||||
label = "error ends with"
|
|
||||||
else:
|
|
||||||
label = "error contains"
|
|
||||||
crit.append(f"{label} '{ov.match_error_contains}'")
|
|
||||||
if crit:
|
if crit:
|
||||||
scope = scope + " [" + ", ".join(crit) + "]"
|
scope = scope + " [" + ", ".join(crit) + "]"
|
||||||
|
|
||||||
@ -104,13 +95,6 @@ def overrides():
|
|||||||
"comment": ov.comment or "",
|
"comment": ov.comment or "",
|
||||||
"match_status": ov.match_status or "",
|
"match_status": ov.match_status or "",
|
||||||
"match_error_contains": ov.match_error_contains or "",
|
"match_error_contains": ov.match_error_contains or "",
|
||||||
"match_error_mode": getattr(ov, "match_error_mode", None) or "",
|
|
||||||
"backup_software": ov.backup_software or "",
|
|
||||||
"backup_type": ov.backup_type or "",
|
|
||||||
"job_id": ov.job_id or "",
|
|
||||||
"object_name": ov.object_name or "",
|
|
||||||
"start_at_raw": (ov.start_at.strftime("%Y-%m-%dT%H:%M") if ov.start_at else ""),
|
|
||||||
"end_at_raw": (ov.end_at.strftime("%Y-%m-%dT%H:%M") if ov.end_at else ""),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -142,12 +126,6 @@ def overrides_create():
|
|||||||
|
|
||||||
match_status = (request.form.get("match_status") or "").strip() or None
|
match_status = (request.form.get("match_status") or "").strip() or None
|
||||||
match_error_contains = (request.form.get("match_error_contains") or "").strip() or None
|
match_error_contains = (request.form.get("match_error_contains") or "").strip() or None
|
||||||
match_error_mode = (request.form.get("match_error_mode") or "").strip().lower() or None
|
|
||||||
if match_error_contains:
|
|
||||||
if match_error_mode not in ("contains", "exact", "starts_with", "ends_with"):
|
|
||||||
match_error_mode = "contains"
|
|
||||||
else:
|
|
||||||
match_error_mode = None
|
|
||||||
|
|
||||||
start_at_str = request.form.get("start_at") or ""
|
start_at_str = request.form.get("start_at") or ""
|
||||||
end_at_str = request.form.get("end_at") or ""
|
end_at_str = request.form.get("end_at") or ""
|
||||||
@ -181,7 +159,6 @@ def overrides_create():
|
|||||||
object_name=object_name if level == "object" else None,
|
object_name=object_name if level == "object" else None,
|
||||||
match_status=match_status,
|
match_status=match_status,
|
||||||
match_error_contains=match_error_contains,
|
match_error_contains=match_error_contains,
|
||||||
match_error_mode=match_error_mode,
|
|
||||||
treat_as_success=treat_as_success,
|
treat_as_success=treat_as_success,
|
||||||
active=True,
|
active=True,
|
||||||
comment=comment,
|
comment=comment,
|
||||||
@ -241,12 +218,6 @@ def overrides_update(override_id: int):
|
|||||||
|
|
||||||
match_status = (request.form.get("match_status") or "").strip() or None
|
match_status = (request.form.get("match_status") or "").strip() or None
|
||||||
match_error_contains = (request.form.get("match_error_contains") or "").strip() or None
|
match_error_contains = (request.form.get("match_error_contains") or "").strip() or None
|
||||||
match_error_mode = (request.form.get("match_error_mode") or "").strip().lower() or None
|
|
||||||
if match_error_contains:
|
|
||||||
if match_error_mode not in ("contains", "exact", "starts_with", "ends_with"):
|
|
||||||
match_error_mode = "contains"
|
|
||||||
else:
|
|
||||||
match_error_mode = None
|
|
||||||
|
|
||||||
start_at_str = request.form.get("start_at") or ""
|
start_at_str = request.form.get("start_at") or ""
|
||||||
end_at_str = request.form.get("end_at") or ""
|
end_at_str = request.form.get("end_at") or ""
|
||||||
@ -281,7 +252,6 @@ def overrides_update(override_id: int):
|
|||||||
ov.object_name = object_name if level == "object" else None
|
ov.object_name = object_name if level == "object" else None
|
||||||
ov.match_status = match_status
|
ov.match_status = match_status
|
||||||
ov.match_error_contains = match_error_contains
|
ov.match_error_contains = match_error_contains
|
||||||
ov.match_error_mode = match_error_mode
|
|
||||||
ov.treat_as_success = treat_as_success
|
ov.treat_as_success = treat_as_success
|
||||||
ov.comment = comment
|
ov.comment = comment
|
||||||
ov.start_at = start_at
|
ov.start_at = start_at
|
||||||
|
|||||||
@ -968,7 +968,6 @@ def api_run_checks_mark_success_override():
|
|||||||
object_name=obj_name,
|
object_name=obj_name,
|
||||||
match_status=(obj_status or None),
|
match_status=(obj_status or None),
|
||||||
match_error_contains=(err[:255] if err else None),
|
match_error_contains=(err[:255] if err else None),
|
||||||
match_error_mode=("contains" if err else None),
|
|
||||||
treat_as_success=True,
|
treat_as_success=True,
|
||||||
active=True,
|
active=True,
|
||||||
comment=comment,
|
comment=comment,
|
||||||
@ -1000,7 +999,6 @@ def api_run_checks_mark_success_override():
|
|||||||
backup_type=job.backup_type or None,
|
backup_type=job.backup_type or None,
|
||||||
match_status=(getattr(run, "status", None) or None),
|
match_status=(getattr(run, "status", None) or None),
|
||||||
match_error_contains=(match_error_contains[:255] if match_error_contains else None),
|
match_error_contains=(match_error_contains[:255] if match_error_contains else None),
|
||||||
match_error_mode=("contains" if match_error_contains else None),
|
|
||||||
treat_as_success=True,
|
treat_as_success=True,
|
||||||
active=True,
|
active=True,
|
||||||
comment=comment,
|
comment=comment,
|
||||||
|
|||||||
@ -293,8 +293,7 @@ def _apply_overrides_to_run(job: Job, run: JobRun):
|
|||||||
try:
|
try:
|
||||||
mec = (getattr(ov, "match_error_contains", None) or "").strip()
|
mec = (getattr(ov, "match_error_contains", None) or "").strip()
|
||||||
if mec:
|
if mec:
|
||||||
mem = (getattr(ov, "match_error_mode", None) or "contains").strip()
|
parts.append(f"contains={mec}")
|
||||||
parts.append(f"error_{mem}={mec}")
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
@ -343,40 +342,6 @@ def _apply_overrides_to_run(job: Job, run: JobRun):
|
|||||||
return False
|
return False
|
||||||
return needle.lower() in haystack.lower()
|
return needle.lower() in haystack.lower()
|
||||||
|
|
||||||
def _matches_error_text(haystack: str | None, needle: str | None, mode: str | None) -> bool:
|
|
||||||
"""Match error text using a configured mode.
|
|
||||||
|
|
||||||
Modes:
|
|
||||||
- contains (default)
|
|
||||||
- exact
|
|
||||||
- starts_with
|
|
||||||
- ends_with
|
|
||||||
|
|
||||||
Matching is case-insensitive and trims surrounding whitespace.
|
|
||||||
"""
|
|
||||||
if not needle:
|
|
||||||
return True
|
|
||||||
if not haystack:
|
|
||||||
return False
|
|
||||||
|
|
||||||
hs = (haystack or "").strip()
|
|
||||||
nd = (needle or "").strip()
|
|
||||||
if not hs:
|
|
||||||
return False
|
|
||||||
|
|
||||||
hs_l = hs.lower()
|
|
||||||
nd_l = nd.lower()
|
|
||||||
m = (mode or "contains").strip().lower()
|
|
||||||
|
|
||||||
if m == "exact":
|
|
||||||
return hs_l == nd_l
|
|
||||||
if m in ("starts_with", "startswith", "start"):
|
|
||||||
return hs_l.startswith(nd_l)
|
|
||||||
if m in ("ends_with", "endswith", "end"):
|
|
||||||
return hs_l.endswith(nd_l)
|
|
||||||
# Default/fallback
|
|
||||||
return nd_l in hs_l
|
|
||||||
|
|
||||||
def _matches_status(candidate: str | None, expected: str | None) -> bool:
|
def _matches_status(candidate: str | None, expected: str | None) -> bool:
|
||||||
if not expected:
|
if not expected:
|
||||||
return True
|
return True
|
||||||
@ -445,12 +410,12 @@ def _apply_overrides_to_run(job: Job, run: JobRun):
|
|||||||
|
|
||||||
# Global overrides should match both the run-level remark and any object-level error messages.
|
# Global overrides should match both the run-level remark and any object-level error messages.
|
||||||
if ov.match_error_contains:
|
if ov.match_error_contains:
|
||||||
if _matches_error_text(run.remark, ov.match_error_contains, getattr(ov, "match_error_mode", None)):
|
if _contains(run.remark, ov.match_error_contains):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check persisted run-object error messages.
|
# Check persisted run-object error messages.
|
||||||
for row in run_object_rows or []:
|
for row in run_object_rows or []:
|
||||||
if _matches_error_text(row.get("error_message"), ov.match_error_contains, getattr(ov, "match_error_mode", None)):
|
if _contains(row.get("error_message"), ov.match_error_contains):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
objs = []
|
objs = []
|
||||||
@ -459,7 +424,7 @@ def _apply_overrides_to_run(job: Job, run: JobRun):
|
|||||||
except Exception:
|
except Exception:
|
||||||
objs = []
|
objs = []
|
||||||
for obj in objs or []:
|
for obj in objs or []:
|
||||||
if _matches_error_text(getattr(obj, "error_message", None), ov.match_error_contains, getattr(ov, "match_error_mode", None)):
|
if _contains(getattr(obj, "error_message", None), ov.match_error_contains):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -473,7 +438,7 @@ def _apply_overrides_to_run(job: Job, run: JobRun):
|
|||||||
continue
|
continue
|
||||||
if not _matches_status(row.get("status"), ov.match_status):
|
if not _matches_status(row.get("status"), ov.match_status):
|
||||||
continue
|
continue
|
||||||
if not _matches_error_text(row.get("error_message"), ov.match_error_contains, getattr(ov, "match_error_mode", None)):
|
if not _contains(row.get("error_message"), ov.match_error_contains):
|
||||||
continue
|
continue
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -488,7 +453,7 @@ def _apply_overrides_to_run(job: Job, run: JobRun):
|
|||||||
continue
|
continue
|
||||||
if not _matches_status(getattr(obj, "status", None), ov.match_status):
|
if not _matches_status(getattr(obj, "status", None), ov.match_status):
|
||||||
continue
|
continue
|
||||||
if not _matches_error_text(getattr(obj, "error_message", None), ov.match_error_contains, getattr(ov, "match_error_mode", None)):
|
if not _contains(getattr(obj, "error_message", None), ov.match_error_contains):
|
||||||
continue
|
continue
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@ -378,7 +378,7 @@ def migrate_remarks_active_from_date() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def migrate_overrides_match_columns() -> None:
|
def migrate_overrides_match_columns() -> None:
|
||||||
"""Add match_status / match_error columns to overrides table if missing."""
|
"""Add match_status and match_error_contains columns to overrides table if missing."""
|
||||||
engine = db.get_engine()
|
engine = db.get_engine()
|
||||||
inspector = inspect(engine)
|
inspector = inspect(engine)
|
||||||
try:
|
try:
|
||||||
@ -397,25 +397,6 @@ def migrate_overrides_match_columns() -> None:
|
|||||||
print("[migrations] Adding overrides.match_error_contains column...")
|
print("[migrations] Adding overrides.match_error_contains column...")
|
||||||
conn.execute(text('ALTER TABLE "overrides" ADD COLUMN match_error_contains VARCHAR(255)'))
|
conn.execute(text('ALTER TABLE "overrides" ADD COLUMN match_error_contains VARCHAR(255)'))
|
||||||
|
|
||||||
if "match_error_mode" not in existing_columns:
|
|
||||||
print("[migrations] Adding overrides.match_error_mode column...")
|
|
||||||
conn.execute(text('ALTER TABLE "overrides" ADD COLUMN match_error_mode VARCHAR(20)'))
|
|
||||||
|
|
||||||
# Backfill mode for existing overrides that already have a match string.
|
|
||||||
try:
|
|
||||||
conn.execute(
|
|
||||||
text(
|
|
||||||
"""
|
|
||||||
UPDATE "overrides"
|
|
||||||
SET match_error_mode = 'contains'
|
|
||||||
WHERE (match_error_mode IS NULL OR match_error_mode = '')
|
|
||||||
AND (match_error_contains IS NOT NULL AND match_error_contains <> '');
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
print("[migrations] migrate_overrides_match_columns completed.")
|
print("[migrations] migrate_overrides_match_columns completed.")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -156,8 +156,6 @@ class Override(db.Model):
|
|||||||
# Matching criteria on object status / error message
|
# Matching criteria on object status / error message
|
||||||
match_status = db.Column(db.String(32), nullable=True)
|
match_status = db.Column(db.String(32), nullable=True)
|
||||||
match_error_contains = db.Column(db.String(255), nullable=True)
|
match_error_contains = db.Column(db.String(255), nullable=True)
|
||||||
# Matching mode for error text: contains (default), exact, starts_with, ends_with
|
|
||||||
match_error_mode = db.Column(db.String(20), nullable=True)
|
|
||||||
|
|
||||||
# Behaviour flags
|
# Behaviour flags
|
||||||
treat_as_success = db.Column(db.Boolean, nullable=False, default=True)
|
treat_as_success = db.Column(db.Boolean, nullable=False, default=True)
|
||||||
|
|||||||
@ -62,16 +62,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<label for="ov_match_error_mode" class="form-label">Error match type</label>
|
<label for="ov_match_error_contains" class="form-label">Error contains</label>
|
||||||
<select class="form-select" id="ov_match_error_mode" name="match_error_mode">
|
|
||||||
<option value="contains">Contains</option>
|
|
||||||
<option value="exact">Exact</option>
|
|
||||||
<option value="starts_with">Starts with</option>
|
|
||||||
<option value="ends_with">Ends with</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<label for="ov_match_error_contains" class="form-label">Error text</label>
|
|
||||||
<input type="text" class="form-control" id="ov_match_error_contains" name="match_error_contains" placeholder="Text to match in error message">
|
<input type="text" class="form-control" id="ov_match_error_contains" name="match_error_contains" placeholder="Text to match in error message">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
@ -151,7 +142,6 @@
|
|||||||
data-ov-object-name="{{ ov.object_name or '' }}"
|
data-ov-object-name="{{ ov.object_name or '' }}"
|
||||||
data-ov-match-status="{{ ov.match_status or '' }}"
|
data-ov-match-status="{{ ov.match_status or '' }}"
|
||||||
data-ov-match-error-contains="{{ ov.match_error_contains or '' }}"
|
data-ov-match-error-contains="{{ ov.match_error_contains or '' }}"
|
||||||
data-ov-match-error-mode="{{ ov.match_error_mode or 'contains' }}"
|
|
||||||
data-ov-treat-as-success="{{ 1 if ov.treat_as_success else 0 }}"
|
data-ov-treat-as-success="{{ 1 if ov.treat_as_success else 0 }}"
|
||||||
data-ov-comment="{{ ov.comment or '' }}"
|
data-ov-comment="{{ ov.comment or '' }}"
|
||||||
data-ov-start-at="{{ ov.start_at_raw or '' }}"
|
data-ov-start-at="{{ ov.start_at_raw or '' }}"
|
||||||
@ -200,7 +190,6 @@
|
|||||||
const jobField = document.getElementById('ov_job_id');
|
const jobField = document.getElementById('ov_job_id');
|
||||||
const objectNameField = document.getElementById('ov_object_name');
|
const objectNameField = document.getElementById('ov_object_name');
|
||||||
const matchStatusField = document.getElementById('ov_match_status');
|
const matchStatusField = document.getElementById('ov_match_status');
|
||||||
const matchErrorModeField = document.getElementById('ov_match_error_mode');
|
|
||||||
const matchErrorContainsField = document.getElementById('ov_match_error_contains');
|
const matchErrorContainsField = document.getElementById('ov_match_error_contains');
|
||||||
const treatAsSuccessField = document.getElementById('ov_treat_success');
|
const treatAsSuccessField = document.getElementById('ov_treat_success');
|
||||||
const commentField = document.getElementById('ov_comment');
|
const commentField = document.getElementById('ov_comment');
|
||||||
@ -239,7 +228,6 @@
|
|||||||
setValue(jobField, btn.dataset.ovJobId || '');
|
setValue(jobField, btn.dataset.ovJobId || '');
|
||||||
setValue(objectNameField, btn.dataset.ovObjectName || '');
|
setValue(objectNameField, btn.dataset.ovObjectName || '');
|
||||||
setValue(matchStatusField, btn.dataset.ovMatchStatus || '');
|
setValue(matchStatusField, btn.dataset.ovMatchStatus || '');
|
||||||
setValue(matchErrorModeField, btn.dataset.ovMatchErrorMode || 'contains');
|
|
||||||
setValue(matchErrorContainsField, btn.dataset.ovMatchErrorContains || '');
|
setValue(matchErrorContainsField, btn.dataset.ovMatchErrorContains || '');
|
||||||
if (treatAsSuccessField) treatAsSuccessField.checked = (btn.dataset.ovTreatAsSuccess === '1');
|
if (treatAsSuccessField) treatAsSuccessField.checked = (btn.dataset.ovTreatAsSuccess === '1');
|
||||||
setValue(commentField, btn.dataset.ovComment || '');
|
setValue(commentField, btn.dataset.ovComment || '');
|
||||||
|
|||||||
@ -35,14 +35,6 @@
|
|||||||
- Updated menu rendering to hide all unauthorized menu items for Reporter users.
|
- Updated menu rendering to hide all unauthorized menu items for Reporter users.
|
||||||
- Adjusted route access to ensure Feedback pages are accessible for the Reporter role.
|
- Adjusted route access to ensure Feedback pages are accessible for the Reporter role.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v20260113-06-overrides-error-match-modes
|
|
||||||
- Added configurable error text matching modes for overrides: contains, exact, starts with, ends with
|
|
||||||
- Updated override evaluation logic to apply the selected match mode across run remarks and object error messages
|
|
||||||
- Extended overrides UI with a match type selector and improved edit support for existing overrides
|
|
||||||
- Added database migration to create and backfill overrides.match_error_mode for existing records
|
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
## v0.1.20
|
## v0.1.20
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user