Auto-commit local changes before build (2026-01-16 12:28:07)
This commit is contained in:
parent
9399082231
commit
b46b7fbc21
@ -1 +1 @@
|
|||||||
v20260116-03-autotask-ticket-linking-visibility
|
v20260116-04-runchecks-autotask-ticket-polling
|
||||||
|
|||||||
@ -467,6 +467,110 @@ class AutotaskClient:
|
|||||||
return data
|
return data
|
||||||
raise AutotaskError("Autotask did not return a ticket object.")
|
raise AutotaskError("Autotask did not return a ticket object.")
|
||||||
|
|
||||||
|
def query_tickets_by_ids(
|
||||||
|
self,
|
||||||
|
ticket_ids: List[int],
|
||||||
|
*,
|
||||||
|
max_records_per_query: int = 200,
|
||||||
|
corr_id: Optional[str] = None,
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""Fetch multiple Tickets by id.
|
||||||
|
|
||||||
|
Preferred path:
|
||||||
|
- Use GET Tickets/query with an 'in' filter over id.
|
||||||
|
|
||||||
|
Fallback path:
|
||||||
|
- If the tenant does not support 'in' queries, fetch tickets individually
|
||||||
|
via GET Tickets/<id>.
|
||||||
|
|
||||||
|
Returns a list of ticket objects (dicts) for tickets that exist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Normalize ids
|
||||||
|
ids: List[int] = []
|
||||||
|
for x in ticket_ids or []:
|
||||||
|
try:
|
||||||
|
xi = int(x)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if xi > 0:
|
||||||
|
ids.append(xi)
|
||||||
|
|
||||||
|
# De-duplicate while preserving order
|
||||||
|
seen = set()
|
||||||
|
dedup: List[int] = []
|
||||||
|
for xi in ids:
|
||||||
|
if xi in seen:
|
||||||
|
continue
|
||||||
|
seen.add(xi)
|
||||||
|
dedup.append(xi)
|
||||||
|
|
||||||
|
if not dedup:
|
||||||
|
return []
|
||||||
|
|
||||||
|
corr = corr_id or uuid.uuid4().hex[:10]
|
||||||
|
|
||||||
|
def _chunk(lst: List[int], n: int) -> List[List[int]]:
|
||||||
|
return [lst[i : i + n] for i in range(0, len(lst), n)]
|
||||||
|
|
||||||
|
out: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
|
# Try query with op=in first (chunked)
|
||||||
|
try:
|
||||||
|
for chunk in _chunk(dedup, max(1, int(max_records_per_query))):
|
||||||
|
search_payload: Dict[str, Any] = {
|
||||||
|
"filter": [
|
||||||
|
{"op": "in", "field": "id", "value": chunk},
|
||||||
|
],
|
||||||
|
"maxRecords": len(chunk),
|
||||||
|
}
|
||||||
|
params = {"search": json.dumps(search_payload)}
|
||||||
|
if self._debug_enabled():
|
||||||
|
logger.info(
|
||||||
|
"[autotask][%s] Tickets/query ids payload=%s",
|
||||||
|
corr,
|
||||||
|
self._safe_json_preview(search_payload, max_chars=1200),
|
||||||
|
)
|
||||||
|
data = self._request("GET", "Tickets/query", params=params)
|
||||||
|
items = self._as_items_list(data)
|
||||||
|
for it in items:
|
||||||
|
if isinstance(it, dict) and it:
|
||||||
|
out.append(it)
|
||||||
|
return out
|
||||||
|
except AutotaskError as exc:
|
||||||
|
# Common tenant behavior: reject op=in with HTTP 400.
|
||||||
|
if self._debug_enabled():
|
||||||
|
logger.info(
|
||||||
|
"[autotask][%s] Tickets/query ids op=in failed; falling back to per-ticket GET. error=%s",
|
||||||
|
corr,
|
||||||
|
str(exc),
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
if self._debug_enabled():
|
||||||
|
logger.info(
|
||||||
|
"[autotask][%s] Tickets/query ids unexpected error; falling back. error=%s",
|
||||||
|
corr,
|
||||||
|
str(exc),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fallback: individual GET calls (best-effort)
|
||||||
|
for tid in dedup:
|
||||||
|
try:
|
||||||
|
t = self.get_ticket(int(tid))
|
||||||
|
if isinstance(t, dict) and t:
|
||||||
|
out.append(t)
|
||||||
|
except AutotaskError as exc:
|
||||||
|
# 404 -> deleted/missing ticket, ignore
|
||||||
|
if getattr(exc, "status_code", None) == 404:
|
||||||
|
continue
|
||||||
|
# Any other error: continue best-effort
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def _lookup_created_ticket_id(
|
def _lookup_created_ticket_id(
|
||||||
self,
|
self,
|
||||||
tracking_identifier: str,
|
tracking_identifier: str,
|
||||||
|
|||||||
@ -876,6 +876,110 @@ def run_checks_details():
|
|||||||
return jsonify({"status": "ok", "job": job_payload, "runs": runs_payload})
|
return jsonify({"status": "ok", "job": job_payload, "runs": runs_payload})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@main_bp.get("/api/run-checks/autotask-ticket-poll")
|
||||||
|
@login_required
|
||||||
|
@roles_required("admin", "operator")
|
||||||
|
def api_run_checks_autotask_ticket_poll():
|
||||||
|
"""Read-only polling of Autotask ticket state for Run Checks.
|
||||||
|
|
||||||
|
Important:
|
||||||
|
- No Backupchecks state is modified.
|
||||||
|
- No mutations are performed in Autotask.
|
||||||
|
- This endpoint is intended to be called only from the Run Checks page.
|
||||||
|
"""
|
||||||
|
|
||||||
|
include_reviewed = False
|
||||||
|
if get_active_role() == "admin":
|
||||||
|
include_reviewed = request.args.get("include_reviewed", "0") in ("1", "true", "yes", "on")
|
||||||
|
|
||||||
|
# Only consider recently relevant runs to keep the payload small.
|
||||||
|
# We intentionally avoid unbounded history polling.
|
||||||
|
days = 60
|
||||||
|
try:
|
||||||
|
days = int(request.args.get("days", "60"))
|
||||||
|
except Exception:
|
||||||
|
days = 60
|
||||||
|
if days < 1:
|
||||||
|
days = 1
|
||||||
|
if days > 180:
|
||||||
|
days = 180
|
||||||
|
|
||||||
|
now_utc = datetime.utcnow().replace(tzinfo=None)
|
||||||
|
window_start = now_utc - timedelta(days=days)
|
||||||
|
|
||||||
|
q = JobRun.query.filter(JobRun.autotask_ticket_id.isnot(None))
|
||||||
|
if not include_reviewed:
|
||||||
|
q = q.filter(JobRun.reviewed_at.is_(None))
|
||||||
|
|
||||||
|
# Only poll runs in our time window.
|
||||||
|
q = q.filter(func.coalesce(JobRun.run_at, JobRun.created_at) >= window_start)
|
||||||
|
|
||||||
|
runs = (
|
||||||
|
q.order_by(func.coalesce(JobRun.run_at, JobRun.created_at).desc(), JobRun.id.desc())
|
||||||
|
.limit(400)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
ticket_ids = []
|
||||||
|
seen = set()
|
||||||
|
for r in runs:
|
||||||
|
tid = getattr(r, "autotask_ticket_id", None)
|
||||||
|
try:
|
||||||
|
tid_int = int(tid)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if tid_int <= 0 or tid_int in seen:
|
||||||
|
continue
|
||||||
|
seen.add(tid_int)
|
||||||
|
ticket_ids.append(tid_int)
|
||||||
|
|
||||||
|
if not ticket_ids:
|
||||||
|
return jsonify({"status": "ok", "tickets": []})
|
||||||
|
|
||||||
|
# If integration is disabled, do not fail the page.
|
||||||
|
settings = _get_or_create_settings()
|
||||||
|
if not getattr(settings, "autotask_enabled", False):
|
||||||
|
return jsonify({"status": "ok", "tickets": [], "autotask_enabled": False})
|
||||||
|
|
||||||
|
try:
|
||||||
|
client = _build_autotask_client_from_settings()
|
||||||
|
except Exception as exc:
|
||||||
|
return jsonify({"status": "ok", "tickets": [], "autotask_enabled": True, "message": str(exc)})
|
||||||
|
|
||||||
|
corr_id = datetime.utcnow().strftime("rcpoll-%Y%m%d%H%M%S")
|
||||||
|
|
||||||
|
# Query tickets in Autotask (best-effort)
|
||||||
|
tickets = []
|
||||||
|
try:
|
||||||
|
tickets = client.query_tickets_by_ids(ticket_ids, corr_id=corr_id)
|
||||||
|
except Exception:
|
||||||
|
tickets = []
|
||||||
|
|
||||||
|
# Build a minimal payload for UI use.
|
||||||
|
out = []
|
||||||
|
for t in tickets or []:
|
||||||
|
if not isinstance(t, dict):
|
||||||
|
continue
|
||||||
|
tid = t.get("id")
|
||||||
|
try:
|
||||||
|
tid_int = int(tid)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
out.append(
|
||||||
|
{
|
||||||
|
"id": tid_int,
|
||||||
|
"ticketNumber": (t.get("ticketNumber") or t.get("TicketNumber") or "") or "",
|
||||||
|
"status": t.get("status"),
|
||||||
|
"statusName": (t.get("statusName") or t.get("StatusName") or "") or "",
|
||||||
|
"title": (t.get("title") or t.get("Title") or "") or "",
|
||||||
|
"lastActivityDate": (t.get("lastActivityDate") or t.get("LastActivityDate") or t.get("lastActivity") or "") or "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return jsonify({"status": "ok", "tickets": out, "autotask_enabled": True})
|
||||||
@main_bp.post("/api/run-checks/autotask-ticket")
|
@main_bp.post("/api/run-checks/autotask-ticket")
|
||||||
@login_required
|
@login_required
|
||||||
@roles_required("admin", "operator")
|
@roles_required("admin", "operator")
|
||||||
|
|||||||
@ -297,9 +297,47 @@
|
|||||||
var currentRunId = null;
|
var currentRunId = null;
|
||||||
var currentPayload = null;
|
var currentPayload = null;
|
||||||
|
|
||||||
|
// Phase 2.1: Read-only Autotask ticket polling (Run Checks page only)
|
||||||
|
// Cache shape: { <ticketId>: {id, ticketNumber, status, statusName, title, lastActivityDate} }
|
||||||
|
var autotaskTicketPollCache = {};
|
||||||
|
|
||||||
|
function pollAutotaskTicketsOnPageOpen() {
|
||||||
|
// Only execute on Run Checks page load.
|
||||||
|
var url = '/api/run-checks/autotask-ticket-poll';
|
||||||
|
var qs = [];
|
||||||
|
// include_reviewed is only meaningful for admins
|
||||||
|
try {
|
||||||
|
var includeReviewed = {{ 'true' if include_reviewed else 'false' }};
|
||||||
|
if (includeReviewed) qs.push('include_reviewed=1');
|
||||||
|
} catch (e) {}
|
||||||
|
if (qs.length) url += '?' + qs.join('&');
|
||||||
|
|
||||||
|
fetch(url)
|
||||||
|
.then(function (r) { return r.json(); })
|
||||||
|
.then(function (j) {
|
||||||
|
if (!j || j.status !== 'ok') return;
|
||||||
|
autotaskTicketPollCache = {};
|
||||||
|
var list = (j.tickets || []);
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
var t = list[i] || {};
|
||||||
|
var id = parseInt(t.id, 10);
|
||||||
|
if (!Number.isFinite(id) || id <= 0) continue;
|
||||||
|
autotaskTicketPollCache[id] = t;
|
||||||
|
}
|
||||||
|
window.__rcAutotaskTicketPollCache = autotaskTicketPollCache;
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
autotaskTicketPollCache = {};
|
||||||
|
window.__rcAutotaskTicketPollCache = autotaskTicketPollCache;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var btnMarkAllReviewed = document.getElementById('rcm_mark_all_reviewed');
|
var btnMarkAllReviewed = document.getElementById('rcm_mark_all_reviewed');
|
||||||
var btnMarkSuccessOverride = document.getElementById('rcm_mark_success_override');
|
var btnMarkSuccessOverride = document.getElementById('rcm_mark_success_override');
|
||||||
|
|
||||||
|
pollAutotaskTicketsOnPageOpen();
|
||||||
|
|
||||||
// Shift-click range selection for checkbox rows
|
// Shift-click range selection for checkbox rows
|
||||||
var lastCheckedCb = null;
|
var lastCheckedCb = null;
|
||||||
|
|
||||||
@ -864,13 +902,33 @@ table.addEventListener('change', function (e) {
|
|||||||
function renderAutotaskInfo(run) {
|
function renderAutotaskInfo(run) {
|
||||||
if (!atInfo) return;
|
if (!atInfo) return;
|
||||||
var num = (run && run.autotask_ticket_number) ? String(run.autotask_ticket_number) : '';
|
var num = (run && run.autotask_ticket_number) ? String(run.autotask_ticket_number) : '';
|
||||||
|
var tid = (run && run.autotask_ticket_id) ? parseInt(run.autotask_ticket_id, 10) : null;
|
||||||
|
var polled = (tid && autotaskTicketPollCache && autotaskTicketPollCache[tid]) ? autotaskTicketPollCache[tid] : null;
|
||||||
|
|
||||||
|
var lines = [];
|
||||||
if (num) {
|
if (num) {
|
||||||
atInfo.innerHTML = '<div><strong>Ticket:</strong> ' + escapeHtml(num) + '</div>';
|
lines.push('<div><strong>Ticket:</strong> ' + escapeHtml(num) + '</div>');
|
||||||
} else if (run && run.autotask_ticket_id) {
|
} else if (tid) {
|
||||||
atInfo.innerHTML = '<div><strong>Ticket:</strong> created</div>';
|
lines.push('<div><strong>Ticket:</strong> created</div>');
|
||||||
} else {
|
} else {
|
||||||
atInfo.innerHTML = '<div class="text-muted">No Autotask ticket created for this run.</div>';
|
lines.push('<div class="text-muted">No Autotask ticket created for this run.</div>');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 2.1 visibility only: show last polled status if available
|
||||||
|
if (tid) {
|
||||||
|
if (polled) {
|
||||||
|
var statusName = (polled.statusName || '').toString().trim();
|
||||||
|
var statusVal = (polled.status !== undefined && polled.status !== null) ? String(polled.status) : '';
|
||||||
|
var label = statusName ? statusName : (statusVal ? ('Status ' + statusVal) : '');
|
||||||
|
if (label) {
|
||||||
|
lines.push('<div class="text-muted">PSA status (polled): ' + escapeHtml(label) + '</div>');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lines.push('<div class="text-muted">PSA status (polled): not available</div>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
atInfo.innerHTML = lines.join('');
|
||||||
}
|
}
|
||||||
window.__rcmRenderAutotaskInfo = renderAutotaskInfo;
|
window.__rcmRenderAutotaskInfo = renderAutotaskInfo;
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,412 @@
|
|||||||
|
# Backupchecks – Autotask Integration
|
||||||
|
|
||||||
|
## Functional Design
|
||||||
|
|
||||||
|
*Last updated: 2026-01-16*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Scope & Goals
|
||||||
|
|
||||||
|
This document describes the **functional design and agreed decisions** for the first phase of the Autotask integration in Backupchecks.
|
||||||
|
|
||||||
|
Goals for phase 1:
|
||||||
|
|
||||||
|
- Allow operators to **manually create Autotask tickets** from Backupchecks.
|
||||||
|
- Ensure **full operator control** over when a ticket is created.
|
||||||
|
- Prevent ticket spam and duplicate tickets.
|
||||||
|
- Maintain clear ownership between Backupchecks and Autotask.
|
||||||
|
- Provide a safe and auditable way to resolve tickets from Backupchecks.
|
||||||
|
|
||||||
|
Out of scope for phase 1:
|
||||||
|
|
||||||
|
- Automatic ticket creation
|
||||||
|
- Automatic ticket closing on success
|
||||||
|
- Issue correlation across multiple runs
|
||||||
|
- Time entry creation or modification
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Core Principles (Leading)
|
||||||
|
|
||||||
|
These principles apply to all design and implementation choices:
|
||||||
|
|
||||||
|
- Autotask is an **external authoritative system** (PSA).
|
||||||
|
- Backupchecks is a **consumer**, not an owner, of PSA data.
|
||||||
|
- **IDs are leading**, names are display-only.
|
||||||
|
- All PSA mappings are **explicit**, never implicit or automatic.
|
||||||
|
- Operators always retain **manual control**.
|
||||||
|
- Renaming in Autotask must **never break mappings**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Customer ↔ Autotask Company Mapping
|
||||||
|
|
||||||
|
### 3.1 Mapping model
|
||||||
|
|
||||||
|
- Mapping is configured in the **Customers** screen.
|
||||||
|
- Mapping is a **1-to-1 explicit relationship**.
|
||||||
|
- Stored values per customer:
|
||||||
|
- PSA type: autotask
|
||||||
|
- Autotask Company ID (leading)
|
||||||
|
- Autotask Company Name (cached for display)
|
||||||
|
- Last sync timestamp
|
||||||
|
- Mapping status: ok | renamed | missing | invalid
|
||||||
|
|
||||||
|
The Autotask Company ID is the source of truth. The name exists only for UI clarity.
|
||||||
|
|
||||||
|
### 3.2 Name synchronisation
|
||||||
|
|
||||||
|
- If the company name is changed in Autotask:
|
||||||
|
- Backupchecks updates the cached name automatically.
|
||||||
|
- The mapping remains intact.
|
||||||
|
- Backupchecks customer names are independent and never overwritten.
|
||||||
|
|
||||||
|
### 3.3 Failure scenarios
|
||||||
|
|
||||||
|
- Autotask company deleted or inaccessible:
|
||||||
|
- Mapping status becomes invalid.
|
||||||
|
- Ticket creation is blocked.
|
||||||
|
- UI clearly indicates broken mapping.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Ticket Creation Model
|
||||||
|
|
||||||
|
### 4.1 Operator-driven creation
|
||||||
|
|
||||||
|
- Tickets are created only via an explicit operator action.
|
||||||
|
- Location: Run Checks page.
|
||||||
|
- Manual ticket number input is removed.
|
||||||
|
- A new action replaces it: Create Autotask ticket.
|
||||||
|
|
||||||
|
### 4.2 One ticket per run
|
||||||
|
|
||||||
|
- Exactly one ticket per run.
|
||||||
|
- A run can never create multiple tickets.
|
||||||
|
- If a ticket exists:
|
||||||
|
- Creation action is replaced by Open ticket.
|
||||||
|
|
||||||
|
### 4.3 Ticket contents (baseline)
|
||||||
|
|
||||||
|
Minimum ticket fields:
|
||||||
|
|
||||||
|
- Subject: [Backupchecks] - - 
|
||||||
|
- Description:
|
||||||
|
- Run date/time
|
||||||
|
- Backup type and job
|
||||||
|
- Affected objects
|
||||||
|
- Error or warning messages
|
||||||
|
- Reference to Backupchecks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Ticket State Tracking in Backupchecks
|
||||||
|
|
||||||
|
Per run, Backupchecks stores:
|
||||||
|
|
||||||
|
- Autotask Ticket ID
|
||||||
|
- Autotask Ticket Number
|
||||||
|
- Ticket URL
|
||||||
|
- Created by
|
||||||
|
- Created at timestamp
|
||||||
|
- Last known ticket status (snapshot)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Ticket Resolution from Backupchecks
|
||||||
|
|
||||||
|
Backupchecks may resolve a ticket only if:
|
||||||
|
|
||||||
|
- The ticket exists
|
||||||
|
- The ticket is not already closed
|
||||||
|
- No time entries are present
|
||||||
|
|
||||||
|
If time entries exist, the ticket is not closed and an internal system note is added.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Backupchecks Settings
|
||||||
|
|
||||||
|
Settings → Extensions & Integrations → Autotask
|
||||||
|
|
||||||
|
Includes:
|
||||||
|
|
||||||
|
- Enable integration (on/off)
|
||||||
|
- Environment
|
||||||
|
- API credentials
|
||||||
|
- Tracking Identifier
|
||||||
|
- Default queue and status
|
||||||
|
- Priority mapping
|
||||||
|
|
||||||
|
### 7.1 Enable / disable behaviour (mandatory)
|
||||||
|
|
||||||
|
Backupchecks must support switching the Autotask integration **on and off at any time**.
|
||||||
|
|
||||||
|
When Autotask integration is **enabled**:
|
||||||
|
- Autotask actions are available (create ticket, resolve ticket, link existing ticket).
|
||||||
|
- Ticket polling/synchronisation (Phase 2) is active.
|
||||||
|
|
||||||
|
When Autotask integration is **disabled**:
|
||||||
|
- Backupchecks falls back to the manual workflow:
|
||||||
|
- Operators can manually enter ticket numbers.
|
||||||
|
- No Autotask API calls are executed.
|
||||||
|
- No polling is performed.
|
||||||
|
- No resolve action is available (as it would require Autotask calls).
|
||||||
|
- Existing stored Autotask references remain visible for audit/history.
|
||||||
|
|
||||||
|
The enable/disable switch must be reversible and must not require data deletion or migration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Roles & Permissions
|
||||||
|
|
||||||
|
- Admin / Operator: create and resolve tickets
|
||||||
|
- Reporter: view-only access
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Explicit Non-Goals (Phase 1)
|
||||||
|
|
||||||
|
- Automatic ticket creation
|
||||||
|
- Automatic ticket closing
|
||||||
|
- Time entry handling
|
||||||
|
- Multiple tickets per run
|
||||||
|
- PSA-side logic
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Phase 1 Summary
|
||||||
|
|
||||||
|
Phase 1 delivers controlled, operator-driven PSA integration with a strong focus on auditability and predictability.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Phase 2 – Ticket State Synchronisation (PSA-driven)
|
||||||
|
|
||||||
|
This phase introduces **PSA-driven state awareness** for already linked tickets. The goal is to keep Backupchecks aligned with Autotask **without introducing active control or automation**.
|
||||||
|
|
||||||
|
### 11.1 Polling strategy (Run Checks entry point)
|
||||||
|
|
||||||
|
- Polling is executed **only when Autotask integration is enabled**.
|
||||||
|
- When the **Run Checks** page is opened, Backupchecks performs a **targeted poll** to Autotask.
|
||||||
|
|
||||||
|
- When the **Run Checks** page is opened, Backupchecks performs a **targeted poll** to Autotask.
|
||||||
|
- Only tickets that are:
|
||||||
|
- Linked to runs shown on the page, and
|
||||||
|
- Not in a terminal state inside Backupchecks
|
||||||
|
are included.
|
||||||
|
- Tickets already marked as resolved or broken are excluded.
|
||||||
|
|
||||||
|
This prevents unnecessary API calls and limits polling to operator-relevant context.
|
||||||
|
|
||||||
|
### 11.2 Active-ticket-only retrieval
|
||||||
|
|
||||||
|
- Backupchecks only queries tickets that are considered **active** in Autotask.
|
||||||
|
- Completed or closed tickets are not included in the active-ticket query.
|
||||||
|
- This ensures minimal load and avoids repeated retrieval of historical data.
|
||||||
|
|
||||||
|
### 11.3 PSA-driven completion handling
|
||||||
|
|
||||||
|
If Autotask reports a ticket with status **Completed**:
|
||||||
|
|
||||||
|
- The linked run in Backupchecks is automatically marked as **Resolved**.
|
||||||
|
- The resolution is explicitly flagged as:
|
||||||
|
- **Resolved by PSA**
|
||||||
|
- Backupchecks does not add notes or modify the ticket in Autotask.
|
||||||
|
|
||||||
|
UI behaviour:
|
||||||
|
- Operators can clearly see that the resolution originated from the PSA.
|
||||||
|
- A visual indicator highlights that the underlying backup issue may still require verification.
|
||||||
|
|
||||||
|
### 11.4 Operator awareness and follow-up
|
||||||
|
|
||||||
|
When a ticket is resolved by PSA:
|
||||||
|
|
||||||
|
- Backupchecks does not assume the technical issue is resolved.
|
||||||
|
- Operators are expected to:
|
||||||
|
- Review the related run
|
||||||
|
- Decide whether further action is required inside Backupchecks
|
||||||
|
|
||||||
|
No automatic reopening or ticket creation is performed.
|
||||||
|
|
||||||
|
### 11.5 Deleted ticket detection
|
||||||
|
|
||||||
|
If a linked ticket is **deleted in Autotask**:
|
||||||
|
|
||||||
|
- Backupchecks detects this during polling.
|
||||||
|
- The ticket linkage is marked as:
|
||||||
|
- **Deleted in PSA**
|
||||||
|
|
||||||
|
UI behaviour:
|
||||||
|
- A clear warning is shown to the operator.
|
||||||
|
- The historical ticket reference remains visible for audit purposes.
|
||||||
|
- Ticket-related actions are blocked until the operator:
|
||||||
|
- Links a replacement ticket, or
|
||||||
|
- Creates a new Autotask ticket
|
||||||
|
|
||||||
|
### 11.6 Ticket resolution from Backupchecks (operator-driven)
|
||||||
|
|
||||||
|
Phase 2 includes implementation of **manual ticket resolution** from Backupchecks under the already defined Phase 1 rules.
|
||||||
|
|
||||||
|
- Resolution is always an explicit operator action (no automation).
|
||||||
|
- Resolution rules remain leading (see Chapter 6):
|
||||||
|
- Ticket must exist
|
||||||
|
- Ticket must not already be closed
|
||||||
|
- Ticket may only be closed by Backupchecks if **no time entries** exist
|
||||||
|
- If time entries exist, Backupchecks adds an internal system note and leaves the ticket open
|
||||||
|
|
||||||
|
UI behaviour (Run Checks and Job Details):
|
||||||
|
- Show a Resolve ticket action only when a validated Autotask Ticket ID exists.
|
||||||
|
- When resolution succeeds, update the run to Resolved and store:
|
||||||
|
- Resolved timestamp
|
||||||
|
- Resolved by (operator)
|
||||||
|
- Resolution origin: Resolved by Backupchecks
|
||||||
|
|
||||||
|
Important alignment with PSA-driven sync:
|
||||||
|
- If Autotask later reports the ticket as Completed, Backupchecks keeps the run resolved, but the origin remains:
|
||||||
|
- Resolved by Backupchecks
|
||||||
|
- If Autotask reports completion before the operator resolves it, Backupchecks sets:
|
||||||
|
- Resolved by PSA
|
||||||
|
|
||||||
|
### 11.7 Explicit non-goals (Phase 2)
|
||||||
|
|
||||||
|
The following remain explicitly out of scope:
|
||||||
|
|
||||||
|
- Automatic ticket creation
|
||||||
|
- Automatic ticket reopening
|
||||||
|
- Automatic resolution without operator intent
|
||||||
|
- Status pushing from Backupchecks to Autotask (except the explicit Resolve action described above)
|
||||||
|
- Any modification of existing Autotask ticket content (except fixed-format internal system notes used during resolution rules)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Future Design Considerations (Post-Phase 2)
|
||||||
|
|
||||||
|
The following sections describe **future design intent only**. They are explicitly **out of scope for Phase 2** and introduce no implementation commitments.
|
||||||
|
|
||||||
|
### 12.1 Ticket lifecycle awareness (read-only intelligence)
|
||||||
|
|
||||||
|
Objective:
|
||||||
|
Provide better insight into the state of linked tickets without adding actions.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- Periodic read-only retrieval of ticket status
|
||||||
|
- Display of:
|
||||||
|
- Current status
|
||||||
|
- Queue
|
||||||
|
- Assigned resource (owner)
|
||||||
|
|
||||||
|
Shown in:
|
||||||
|
- Run Checks
|
||||||
|
- Job Details
|
||||||
|
- Tickets / Remarks overview
|
||||||
|
|
||||||
|
Explicitly excluded:
|
||||||
|
- No automatic actions
|
||||||
|
- No status synchronisation back to Autotask
|
||||||
|
|
||||||
|
Value:
|
||||||
|
- Operators need to open Autotask less frequently
|
||||||
|
- Faster visibility into whether a ticket has been picked up
|
||||||
|
|
||||||
|
### 12.2 Operator notes & context enrichment
|
||||||
|
|
||||||
|
Objective:
|
||||||
|
Add contextual information to existing tickets without taking ownership away from the PSA.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- Add internal/system notes from Backupchecks
|
||||||
|
- Notes are always written in a fixed format
|
||||||
|
- Notes are added only by explicit operator action
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Never overwrite existing ticket content
|
||||||
|
- Never create customer-facing notes
|
||||||
|
- Never generate notes automatically
|
||||||
|
|
||||||
|
Value:
|
||||||
|
- Tickets remain up to date without duplication
|
||||||
|
- Autotask remains the authoritative system
|
||||||
|
|
||||||
|
### 12.3 Controlled assistance (semi-automatic support)
|
||||||
|
|
||||||
|
Objective:
|
||||||
|
Support operator decision-making without introducing automation.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- Suggestion: a similar error already has an open ticket
|
||||||
|
- Suggestion: previous run was successful, consider resolving the ticket
|
||||||
|
- Highlight repeated failures without any linked ticket
|
||||||
|
|
||||||
|
Important constraints:
|
||||||
|
- Suggestions only
|
||||||
|
- No automatic ticket creation
|
||||||
|
- No automatic ticket closure
|
||||||
|
|
||||||
|
Value:
|
||||||
|
- Reduced human error
|
||||||
|
- No loss of operator control
|
||||||
|
|
||||||
|
### 12.4 Cross-run correlation (analytical)
|
||||||
|
|
||||||
|
Objective:
|
||||||
|
Provide insight into structural or recurring problems before tickets are created.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- Detection of repeated failures across multiple runs
|
||||||
|
- Detection of the same object with the same error over time
|
||||||
|
- Visualisation inside Backupchecks only
|
||||||
|
|
||||||
|
Explicitly excluded:
|
||||||
|
- Ticket bundling
|
||||||
|
- Ticket merging
|
||||||
|
- Any Autotask-side modifications
|
||||||
|
|
||||||
|
Value:
|
||||||
|
- Better decisions prior to ticket creation
|
||||||
|
- Reduced noise in the PSA
|
||||||
|
|
||||||
|
### 12.5 Multi-PSA abstraction (design preparation)
|
||||||
|
|
||||||
|
Objective:
|
||||||
|
Prepare the internal design for future PSA support without implementing it.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- PSA-agnostic internal models:
|
||||||
|
- Ticket
|
||||||
|
- Company mapping
|
||||||
|
- Resolution rules
|
||||||
|
- Autotask remains the only concrete implementation
|
||||||
|
|
||||||
|
Why this is documented now:
|
||||||
|
- Prevents Autotask-specific technical debt
|
||||||
|
- Keeps the architecture open for other PSA platforms
|
||||||
|
|
||||||
|
### 12.6 Governance & audit depth
|
||||||
|
|
||||||
|
Objective:
|
||||||
|
Ensure full traceability for MSP and enterprise environments.
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- Extended audit logging:
|
||||||
|
- Who created, linked or resolved a ticket
|
||||||
|
- When the action occurred
|
||||||
|
- From which run the action originated
|
||||||
|
- Read-only export capabilities
|
||||||
|
- Optional compliance-oriented views
|
||||||
|
|
||||||
|
Value:
|
||||||
|
- MSP and enterprise readiness
|
||||||
|
- Supports internal and external audits
|
||||||
|
|
||||||
|
### 12.7 Explicit non-directions
|
||||||
|
|
||||||
|
The following are intentionally excluded unless a strategic decision is made later:
|
||||||
|
|
||||||
|
- Automatic ticket creation
|
||||||
|
- Automatic ticket handling
|
||||||
|
- Time registration
|
||||||
|
- Mutation of existing ticket content
|
||||||
|
- PSA business logic inside Backupchecks
|
||||||
|
|
||||||
@ -1,464 +0,0 @@
|
|||||||
# Backupchecks – Autotask Integration
|
|
||||||
|
|
||||||
## Functional Design – Phase 1
|
|
||||||
|
|
||||||
_Last updated: 2026-01-13_
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Scope & Goals
|
|
||||||
|
|
||||||
This document describes the **functional design and agreed decisions** for the first phase of the Autotask integration in Backupchecks.
|
|
||||||
|
|
||||||
Goals for phase 1:
|
|
||||||
- Allow operators to **manually create Autotask tickets** from Backupchecks.
|
|
||||||
- Ensure **full operator control** over when a ticket is created.
|
|
||||||
- Prevent ticket spam and duplicate tickets.
|
|
||||||
- Maintain clear ownership between Backupchecks and Autotask.
|
|
||||||
- Provide a safe and auditable way to resolve tickets from Backupchecks.
|
|
||||||
|
|
||||||
Out of scope for phase 1:
|
|
||||||
- Automatic ticket creation
|
|
||||||
- Automatic ticket closing on success
|
|
||||||
- Issue correlation across multiple runs
|
|
||||||
- Time entry creation or modification
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Core Principles (Leading)
|
|
||||||
|
|
||||||
These principles apply to all design and implementation choices:
|
|
||||||
|
|
||||||
- Autotask is an **external authoritative system** (PSA).
|
|
||||||
- Backupchecks is a **consumer**, not an owner, of PSA data.
|
|
||||||
- **IDs are leading**, names are display-only.
|
|
||||||
- All PSA mappings are **explicit**, never implicit or automatic.
|
|
||||||
- Operators always retain **manual control**.
|
|
||||||
- Renaming in Autotask must **never break mappings**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Customer ↔ Autotask Company Mapping
|
|
||||||
|
|
||||||
### 3.1 Mapping model
|
|
||||||
|
|
||||||
- Mapping is configured in the **Customers** screen.
|
|
||||||
- Mapping is a **1-to-1 explicit relationship**.
|
|
||||||
- Stored values per customer:
|
|
||||||
- PSA type: `autotask`
|
|
||||||
- Autotask Company ID (leading)
|
|
||||||
- Autotask Company Name (cached for display)
|
|
||||||
- Last sync timestamp
|
|
||||||
- Mapping status: `ok | renamed | missing | invalid`
|
|
||||||
|
|
||||||
> **Note:** The Autotask Company ID is the source of truth. The name exists only for UI clarity.
|
|
||||||
|
|
||||||
### 3.2 Name synchronisation
|
|
||||||
|
|
||||||
- If the company name is changed in Autotask:
|
|
||||||
- Backupchecks updates the cached name automatically.
|
|
||||||
- The mapping remains intact.
|
|
||||||
- Backupchecks customer names are **independent** and never overwritten.
|
|
||||||
|
|
||||||
### 3.3 Failure scenarios
|
|
||||||
|
|
||||||
- Autotask company deleted or inaccessible:
|
|
||||||
- Mapping status becomes `invalid`.
|
|
||||||
- Ticket creation is blocked.
|
|
||||||
- UI clearly indicates broken mapping.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Ticket Creation Model
|
|
||||||
|
|
||||||
### 4.1 Operator-driven creation
|
|
||||||
|
|
||||||
- Tickets are created **only** via an explicit operator action.
|
|
||||||
- Location: **Run Checks** page.
|
|
||||||
- Manual ticket number input is removed.
|
|
||||||
- A new action replaces it:
|
|
||||||
- **“Create Autotask ticket”**
|
|
||||||
|
|
||||||
> **Rationale:** There are too many backup alerts that do not require a ticket. Human judgement remains essential.
|
|
||||||
|
|
||||||
### 4.2 One ticket per run (Key decision)
|
|
||||||
|
|
||||||
- **Exactly one ticket per Run**.
|
|
||||||
- A run can never create multiple tickets.
|
|
||||||
- If a ticket exists:
|
|
||||||
- Creation action is replaced by:
|
|
||||||
- “Open ticket”
|
|
||||||
- (Later) “Add note”
|
|
||||||
|
|
||||||
> **Rationale:** Multiple errors within a run often share the same root cause. This prevents ticket flooding.
|
|
||||||
|
|
||||||
### 4.3 Ticket contents (baseline)
|
|
||||||
|
|
||||||
Minimum ticket fields:
|
|
||||||
- Subject:
|
|
||||||
- `[Backupchecks] <Customer> - <Job> - <Status>`
|
|
||||||
- Description:
|
|
||||||
- Run date/time
|
|
||||||
- Backup type and job
|
|
||||||
- Affected objects (e.g. HV01, USB Disk)
|
|
||||||
- Error / warning messages
|
|
||||||
- Reference to Backupchecks (URL or identifier)
|
|
||||||
|
|
||||||
Optional (configurable later):
|
|
||||||
- Queue
|
|
||||||
- Issue type / category
|
|
||||||
- Priority mapping
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Ticket State Tracking in Backupchecks
|
|
||||||
|
|
||||||
Per Run, Backupchecks stores:
|
|
||||||
- Autotask Ticket ID
|
|
||||||
- Autotask Ticket Number
|
|
||||||
- Ticket URL (optional)
|
|
||||||
- Created by (operator)
|
|
||||||
- Created at timestamp
|
|
||||||
- Last known ticket status (snapshot)
|
|
||||||
|
|
||||||
This ensures:
|
|
||||||
- No duplicate tickets
|
|
||||||
- Full audit trail
|
|
||||||
n- Clear operator feedback
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5A. Ticket Content Composition Rules
|
|
||||||
|
|
||||||
This chapter defines how Backupchecks determines **what content is placed in an Autotask ticket**, with the explicit goal of keeping tickets readable and actionable.
|
|
||||||
|
|
||||||
### 5A.1 Guiding principle
|
|
||||||
|
|
||||||
- A ticket is a **signal**, not a log file.
|
|
||||||
- The ticket must remain readable for the ticket owner.
|
|
||||||
- Full technical details always remain available in Backupchecks.
|
|
||||||
|
|
||||||
### 5A.2 Content hierarchy (deterministic)
|
|
||||||
|
|
||||||
Backupchecks applies the following strict hierarchy when composing ticket content:
|
|
||||||
|
|
||||||
1. **Overall remark** (run-level summary) – if present, this is leading.
|
|
||||||
2. **Object-level messages** – used only when no overall remark exists.
|
|
||||||
|
|
||||||
This hierarchy is fixed and non-configurable in phase 1.
|
|
||||||
|
|
||||||
### 5A.3 Scenario A – Overall remark present
|
|
||||||
|
|
||||||
If an overall remark exists for the run:
|
|
||||||
- The ticket description contains:
|
|
||||||
- The overall remark
|
|
||||||
- Job name, run date/time, and status
|
|
||||||
- Object-level errors are **not listed in full**.
|
|
||||||
- A short informational line is added:
|
|
||||||
- “Multiple objects reported errors. See Backupchecks for full details.”
|
|
||||||
|
|
||||||
> **Rationale:** The overall remark already represents a consolidated summary. Listing many objects would reduce ticket clarity.
|
|
||||||
|
|
||||||
### 5A.4 Scenario B – No overall remark
|
|
||||||
|
|
||||||
If no overall remark exists:
|
|
||||||
- The ticket description includes object-level errors.
|
|
||||||
- Object listings are **explicitly limited**:
|
|
||||||
- A maximum of *N* objects (exact value defined during implementation)
|
|
||||||
- If more objects are present:
|
|
||||||
- “And X additional objects reported similar errors.”
|
|
||||||
|
|
||||||
> **Rationale:** Prevents large, unreadable tickets while still providing concrete examples.
|
|
||||||
|
|
||||||
### 5A.5 Mandatory reference to Backupchecks
|
|
||||||
|
|
||||||
Every ticket created by Backupchecks must include a **direct link to the Job Details page of the originating run**.
|
|
||||||
|
|
||||||
This link is intended as the primary navigation entry point for the ticket owner.
|
|
||||||
|
|
||||||
The ticket description must include:
|
|
||||||
- Job name
|
|
||||||
- Run date/time
|
|
||||||
- A clickable URL pointing to the Job Details page of that run in Backupchecks
|
|
||||||
|
|
||||||
> **Rationale:** The Job Details page provides the most complete and structured context for investigation.
|
|
||||||
|
|
||||||
This ensures:
|
|
||||||
- Full traceability
|
|
||||||
- Fast access to complete technical details
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5A.6 Explicit exclusions
|
|
||||||
|
|
||||||
The following content is deliberately excluded from ticket descriptions:
|
|
||||||
- Complete object lists when large
|
|
||||||
- Repeated identical error messages
|
|
||||||
- Raw technical dumps or stack traces
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Ticket Resolution from Backupchecks
|
|
||||||
|
|
||||||
### 6.1 Resolution policy
|
|
||||||
|
|
||||||
Backupchecks **may resolve** an Autotask ticket **only if**:
|
|
||||||
- The ticket exists
|
|
||||||
- The ticket is not already closed
|
|
||||||
- **No time entries are present on the ticket**
|
|
||||||
|
|
||||||
This rule is **mandatory and non-configurable**.
|
|
||||||
|
|
||||||
> **Rationale:** Prevents financial and operational conflicts inside Autotask.
|
|
||||||
|
|
||||||
### 6.2 Behaviour when time entries exist
|
|
||||||
|
|
||||||
If an operator clicks **Resolve ticket** but the ticket **contains time entries**:
|
|
||||||
- The ticket **must not be closed** by Backupchecks.
|
|
||||||
- Backupchecks **adds an internal system note** to the ticket stating that it was marked as resolved from Backupchecks.
|
|
||||||
- The ticket remains open for the ticket owner to review and close manually.
|
|
||||||
|
|
||||||
Proposed internal system note text:
|
|
||||||
|
|
||||||
> `Ticket marked as resolved in Backupchecks, but not closed automatically because time entries are present.`
|
|
||||||
|
|
||||||
> **Rationale:** Ensures the ticket owner is explicitly informed without violating Autotask process or financial controls.
|
|
||||||
|
|
||||||
### 6.3 Closing note (fixed text)
|
|
||||||
|
|
||||||
When resolving a ticket **and no time entries are present**, Backupchecks always adds the following **internal system note** **before closing**:
|
|
||||||
|
|
||||||
> `Ticket resolved via Backupchecks after verification that the backup issue is no longer present.`
|
|
||||||
|
|
||||||
Characteristics:
|
|
||||||
- Fixed text (no operator editing in phase 1)
|
|
||||||
- **System / internal note** (never customer-facing)
|
|
||||||
- Ensures auditability and traceability
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6A. Handling Existing Tickets & Compatibility Mode
|
|
||||||
|
|
||||||
### 6A.1 Existing manual ticket numbers
|
|
||||||
|
|
||||||
In the pre-integration workflow, a run may already contain a manually entered ticket number.
|
|
||||||
|
|
||||||
When Autotask integration is **enabled**:
|
|
||||||
- Existing ticket numbers remain visible.
|
|
||||||
- Backupchecks may offer a one-time action:
|
|
||||||
- **“Link existing Autotask ticket”**
|
|
||||||
- This validates the ticket in Autotask and stores the **Autotask Ticket ID**.
|
|
||||||
|
|
||||||
> **Note:** Without an Autotask Ticket ID, Backupchecks must not attempt to resolve a ticket.
|
|
||||||
|
|
||||||
When Autotask integration is **disabled**:
|
|
||||||
- The current/manual workflow applies (manual ticket number entry).
|
|
||||||
|
|
||||||
### 6A.2 Linking existing Autotask tickets
|
|
||||||
|
|
||||||
When integration is enabled, operators can link an existing Autotask ticket to a run:
|
|
||||||
- Search/select a ticket (preferably by ticket number)
|
|
||||||
- Store:
|
|
||||||
- Autotask Ticket ID
|
|
||||||
- Autotask Ticket Number
|
|
||||||
- Ticket URL (optional)
|
|
||||||
|
|
||||||
After linking:
|
|
||||||
- The run behaves like an integration-created ticket for viewing and resolution rules.
|
|
||||||
|
|
||||||
### 6A.3 Compatibility mode (optional setting)
|
|
||||||
|
|
||||||
Optional setting (recommended for transition periods):
|
|
||||||
- **“Allow manual ticket number entry when Autotask is enabled”** (default: OFF)
|
|
||||||
|
|
||||||
Behaviour:
|
|
||||||
- When ON, operators can still manually enter a ticket number even if integration is enabled.
|
|
||||||
- Resolve from Backupchecks is still only possible for tickets that have a validated Autotask Ticket ID.
|
|
||||||
|
|
||||||
> **Rationale:** Provides a safe escape hatch during rollout and migration.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6B. Deleted Tickets in Autotask
|
|
||||||
|
|
||||||
Tickets may be deleted in Autotask. When a ticket referenced by Backupchecks is deleted, the linkage becomes invalid.
|
|
||||||
|
|
||||||
### 6B.1 Detection
|
|
||||||
|
|
||||||
When Backupchecks attempts to fetch the ticket by Autotask Ticket ID:
|
|
||||||
- If Autotask returns “not found” (deleted/missing), Backupchecks marks the linkage as **broken**.
|
|
||||||
|
|
||||||
### 6B.2 Behaviour when a ticket is deleted
|
|
||||||
|
|
||||||
- The run keeps the historical reference (ticket number/ID) for audit purposes.
|
|
||||||
- The ticket state is shown as:
|
|
||||||
- **“Missing in Autotask (deleted)”**
|
|
||||||
- Actions are blocked:
|
|
||||||
- No “Open ticket” (if no valid URL)
|
|
||||||
- No “Resolve ticket”
|
|
||||||
- Operators can choose:
|
|
||||||
- **Re-link to another ticket** (if the ticket was recreated or replaced)
|
|
||||||
- **Create a new Autotask ticket** (creates a new link for that run)
|
|
||||||
|
|
||||||
> **Note:** Backupchecks should never silently remove the stored linkage, to preserve auditability.
|
|
||||||
|
|
||||||
### 6B.3 Optional: periodic validation
|
|
||||||
|
|
||||||
Optionally (later), Backupchecks may periodically validate linked ticket IDs and flag missing tickets.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Backupchecks Settings
|
|
||||||
|
|
||||||
### 7.1 New settings section
|
|
||||||
|
|
||||||
**Settings → Extensions & Integrations → Autotask**
|
|
||||||
|
|
||||||
### 7.2 Required settings
|
|
||||||
|
|
||||||
- Enable Autotask integration (on/off)
|
|
||||||
- Environment: Sandbox / Production
|
|
||||||
- API Username
|
|
||||||
- API Password
|
|
||||||
- Tracking Identifier
|
|
||||||
|
|
||||||
### 7.3 Ticket creation defaults (configurable)
|
|
||||||
|
|
||||||
These defaults are applied when Backupchecks creates a new Autotask ticket.
|
|
||||||
|
|
||||||
- Ticket Source (default): **Monitoring Alert**
|
|
||||||
- Default Queue (default): **Helpdesk**
|
|
||||||
- Default Status (default): **New**
|
|
||||||
- Priority mapping:
|
|
||||||
- Warning → **Medium**
|
|
||||||
- Error → **High**
|
|
||||||
|
|
||||||
> **Note:** Issue Type / Category is intentionally **not set** by Backupchecks and will be assigned by the ticket owner or traffic manager.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 7.3A Backupchecks Base URL
|
|
||||||
|
|
||||||
- Base URL of the Backupchecks instance (e.g. `https://backupchecks.example.com`)
|
|
||||||
|
|
||||||
This value is required to construct:
|
|
||||||
- Direct links to Job Details pages
|
|
||||||
- Stable references inside Autotask tickets
|
|
||||||
|
|
||||||
> **Note:** This setting is mandatory for ticket creation and must be validated.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 7.4 Dynamic reference data
|
|
||||||
|
|
||||||
Backupchecks must retrieve the following reference data from Autotask and present it in Settings:
|
|
||||||
- Available Queues
|
|
||||||
- Available Ticket Sources
|
|
||||||
|
|
||||||
These lists are:
|
|
||||||
- Loaded on demand (or via refresh action)
|
|
||||||
- Stored for selection in Settings
|
|
||||||
|
|
||||||
> **Rationale:** Prevents hard-coded values and keeps Backupchecks aligned with Autotask configuration changes.
|
|
||||||
|
|
||||||
### 7.5 Resolve configuration
|
|
||||||
|
|
||||||
- Allow resolving tickets from Backupchecks (on/off)
|
|
||||||
- Closing note texts (read-only, fixed):
|
|
||||||
- Standard resolve note
|
|
||||||
- Time-entry-blocked resolve note
|
|
||||||
|
|
||||||
### 7.6 Validation & diagnostics
|
|
||||||
|
|
||||||
- Test connection
|
|
||||||
- Validate configuration (credentials, reference data access)
|
|
||||||
- Optional API logging level
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Roles & Permissions
|
|
||||||
|
|
||||||
- Admin / Operator:
|
|
||||||
- Create tickets
|
|
||||||
- Resolve tickets (if allowed)
|
|
||||||
- Reporter:
|
|
||||||
- View ticket number and link
|
|
||||||
- No create or resolve actions
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Handling Existing, Linked and Deleted Tickets
|
|
||||||
|
|
||||||
### 9.1 Existing tickets (pre-integration)
|
|
||||||
|
|
||||||
- Runs that already contain a manually entered ticket number remain valid.
|
|
||||||
- When Autotask integration is enabled, operators may optionally:
|
|
||||||
- Link the run to an existing Autotask ticket (validated against Autotask).
|
|
||||||
- After linking, the run follows the same rules as integration-created tickets.
|
|
||||||
|
|
||||||
> **Note:** This optional compatibility flow exists to support a gradual transition and avoids forced migration.
|
|
||||||
|
|
||||||
### 9.2 Optional compatibility mode
|
|
||||||
|
|
||||||
- Optional setting: **Allow manual ticket number entry when Autotask is enabled**
|
|
||||||
- Default: OFF
|
|
||||||
- Intended as a temporary transition mechanism.
|
|
||||||
|
|
||||||
### 9.3 Deleted tickets in Autotask (important case)
|
|
||||||
|
|
||||||
Tickets may be deleted directly in Autotask. Backupchecks must handle this safely and explicitly.
|
|
||||||
|
|
||||||
Behaviour:
|
|
||||||
- Backupchecks never assumes tickets exist based on stored data alone.
|
|
||||||
- On any ticket-related action (view, resolve, open):
|
|
||||||
- Backupchecks validates the ticket ID against Autotask.
|
|
||||||
|
|
||||||
If Autotask returns *not found*:
|
|
||||||
- The ticket is marked as **Deleted (external)**.
|
|
||||||
- The existing link is preserved as historical data but marked inactive.
|
|
||||||
- No further actions (resolve, update) are allowed on that ticket.
|
|
||||||
|
|
||||||
UI behaviour:
|
|
||||||
- Ticket number remains visible with a clear indicator:
|
|
||||||
- “Ticket deleted in Autotask”
|
|
||||||
- Operator is offered one explicit action:
|
|
||||||
- “Create new Autotask ticket” (results in a new ticket linked to the same run)
|
|
||||||
|
|
||||||
> **Rationale:** Ticket deletion is an external administrative decision. Backupchecks records the fact but does not attempt to repair or hide it.
|
|
||||||
|
|
||||||
### 9.4 Why links are not silently removed
|
|
||||||
|
|
||||||
- Silent removal would break audit trails.
|
|
||||||
- Historical runs must retain context, even if external objects no longer exist.
|
|
||||||
- Operators must explicitly decide how to proceed.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Explicit Non-Goals (Phase 1)
|
|
||||||
|
|
||||||
The following are explicitly excluded:
|
|
||||||
- Automatic ticket creation
|
|
||||||
- Automatic ticket closing
|
|
||||||
- Automatic re-creation of deleted tickets
|
|
||||||
- Updating ticket content after creation
|
|
||||||
- Multiple tickets per run
|
|
||||||
- Time entry handling
|
|
||||||
- Multi-PSA support
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. Phase 1 Summary
|
|
||||||
|
|
||||||
Phase 1 delivers:
|
|
||||||
- Safe, controlled PSA integration
|
|
||||||
- Operator-driven ticket lifecycle
|
|
||||||
- Explicit handling of legacy, linked and deleted tickets
|
|
||||||
- Clear audit trail
|
|
||||||
- Minimal risk to Autotask data integrity
|
|
||||||
|
|
||||||
This design intentionally prioritises **predictability and control** over automation.
|
|
||||||
|
|
||||||
Future phases may build on this foundation.
|
|
||||||
|
|
||||||
432
docs/backupchecks_autotask_integration_phase_2_implementation.md
Normal file
432
docs/backupchecks_autotask_integration_phase_2_implementation.md
Normal file
@ -0,0 +1,432 @@
|
|||||||
|
# Backupchecks – Autotask Integration
|
||||||
|
|
||||||
|
## Phase 2 – Implementation Design
|
||||||
|
|
||||||
|
*Document type: Implementation design*
|
||||||
|
*Scope: Phase 2 only*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Purpose
|
||||||
|
|
||||||
|
This document describes the **technical implementation approach** for Phase 2 of the Autotask integration. Phase 2 focuses on **ticket state synchronisation and operator-driven resolution**, based on the approved functional design.
|
||||||
|
|
||||||
|
No future concepts or post–Phase 2 ideas are included in this document.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Preconditions
|
||||||
|
|
||||||
|
Phase 2 assumes:
|
||||||
|
|
||||||
|
- Phase 1 is fully implemented and deployed
|
||||||
|
- Autotask integration can be enabled and disabled at runtime
|
||||||
|
- Tickets are already linked to runs via Autotask Ticket ID
|
||||||
|
- Backupchecks is not the authoritative system for ticket state
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. High-level Behaviour Overview
|
||||||
|
|
||||||
|
Phase 2 is implemented in **controlled sub-phases**. Each phase introduces a limited set of actions and ends with an explicit **functional validation moment** before continuing.
|
||||||
|
|
||||||
|
The sub-phases are:
|
||||||
|
|
||||||
|
- Phase 2.1 – Read-only ticket polling
|
||||||
|
- Phase 2.2 – PSA-driven resolution handling
|
||||||
|
- Phase 2.3 – Deleted ticket detection
|
||||||
|
- Phase 2.4 – Manual ticket resolution from Backupchecks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Phase 2.1 – Read-only Ticket Polling
|
||||||
|
|
||||||
|
### 4.1 Scope
|
||||||
|
|
||||||
|
This phase introduces **read-only polling** of Autotask ticket state. No mutations or resolution logic is applied.
|
||||||
|
|
||||||
|
### 4.2 Trigger point
|
||||||
|
|
||||||
|
Polling is triggered when:
|
||||||
|
|
||||||
|
- Autotask integration is enabled
|
||||||
|
- The **Run Checks** page is opened
|
||||||
|
|
||||||
|
There is no background scheduler or periodic task.
|
||||||
|
|
||||||
|
### 4.3 Polling scope
|
||||||
|
|
||||||
|
Only tickets that meet **all** of the following criteria are included:
|
||||||
|
|
||||||
|
- Ticket is linked to a run visible on the Run Checks page
|
||||||
|
- Run is not already resolved in Backupchecks
|
||||||
|
- Ticket is not marked as deleted or invalid
|
||||||
|
|
||||||
|
### 4.4 Autotask query strategy
|
||||||
|
|
||||||
|
- Query only **active tickets** in Autotask
|
||||||
|
- Ticket ID is always used as the lookup key
|
||||||
|
|
||||||
|
### 4.5 Control moment – Phase 2.1
|
||||||
|
|
||||||
|
Functional validation:
|
||||||
|
|
||||||
|
- Run Checks page loads without delay
|
||||||
|
- Correct tickets are polled
|
||||||
|
- No state changes occur in Backupchecks
|
||||||
|
- No Autotask data is modified
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.2 Polling scope
|
||||||
|
|
||||||
|
Only tickets that meet **all** of the following criteria are included:
|
||||||
|
|
||||||
|
- Ticket is linked to a run visible on the Run Checks page
|
||||||
|
- Run is not already resolved in Backupchecks
|
||||||
|
- Ticket is not marked as deleted or invalid
|
||||||
|
|
||||||
|
This guarantees:
|
||||||
|
|
||||||
|
- Minimal API usage
|
||||||
|
- Operator-context-only data retrieval
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.3 Autotask query strategy
|
||||||
|
|
||||||
|
- Query only **active tickets** in Autotask
|
||||||
|
- Completed / closed tickets are excluded from the active query
|
||||||
|
- Ticket ID is always used as the lookup key
|
||||||
|
|
||||||
|
If an expected ticket is not returned:
|
||||||
|
|
||||||
|
- A follow-up single-ticket lookup is executed
|
||||||
|
- If still not found, the ticket is treated as **deleted in PSA**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Phase 2.2 – PSA-driven Resolution Handling
|
||||||
|
|
||||||
|
### 5.1 Scope
|
||||||
|
|
||||||
|
This phase adds **state interpretation** on top of read-only polling.
|
||||||
|
|
||||||
|
### 5.2 Completed ticket handling
|
||||||
|
|
||||||
|
If Autotask reports the ticket status as **Completed**:
|
||||||
|
|
||||||
|
- Mark the linked run as **Resolved**
|
||||||
|
- Set resolution origin to:
|
||||||
|
- `Resolved by PSA`
|
||||||
|
- Store resolution timestamp
|
||||||
|
|
||||||
|
No write-back to Autotask is performed.
|
||||||
|
|
||||||
|
### 5.3 Active ticket handling
|
||||||
|
|
||||||
|
If the ticket exists and is active:
|
||||||
|
|
||||||
|
- Update cached ticket status snapshot only
|
||||||
|
- No state change in Backupchecks
|
||||||
|
|
||||||
|
### 5.4 Control moment – Phase 2.2
|
||||||
|
|
||||||
|
Functional validation:
|
||||||
|
|
||||||
|
- PSA-completed tickets resolve runs correctly
|
||||||
|
- Resolution origin is shown correctly
|
||||||
|
- Active tickets do not alter run state
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.2 Completed ticket
|
||||||
|
|
||||||
|
If Autotask reports the ticket status as **Completed**:
|
||||||
|
|
||||||
|
- Mark the linked run as **Resolved**
|
||||||
|
- Set resolution origin to:
|
||||||
|
- `Resolved by PSA`
|
||||||
|
- Store resolution timestamp
|
||||||
|
|
||||||
|
No write-back to Autotask is performed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.3 Deleted or missing ticket
|
||||||
|
|
||||||
|
If the ticket cannot be found in Autotask:
|
||||||
|
|
||||||
|
- Mark ticket linkage as:
|
||||||
|
- `Deleted in PSA`
|
||||||
|
- Block ticket-related actions
|
||||||
|
- Preserve historical references (ID, number, URL)
|
||||||
|
|
||||||
|
Operator must explicitly link or create a replacement ticket.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Phase 2.3 – Deleted Ticket Detection
|
||||||
|
|
||||||
|
### 6.1 Scope
|
||||||
|
|
||||||
|
This phase introduces detection of tickets that are removed from Autotask.
|
||||||
|
|
||||||
|
### 6.2 Detection logic
|
||||||
|
|
||||||
|
If a linked ticket is not returned by the active-ticket query:
|
||||||
|
|
||||||
|
- Execute a single-ticket lookup
|
||||||
|
- If still not found:
|
||||||
|
- Mark ticket linkage as `Deleted in PSA`
|
||||||
|
|
||||||
|
### 6.3 Behaviour
|
||||||
|
|
||||||
|
- Ticket actions are blocked
|
||||||
|
- Historical references remain visible
|
||||||
|
- Operator must explicitly relink or recreate a ticket
|
||||||
|
|
||||||
|
### 6.4 Control moment – Phase 2.3
|
||||||
|
|
||||||
|
Functional validation:
|
||||||
|
|
||||||
|
- Deleted tickets are detected reliably
|
||||||
|
- Warning is visible in UI
|
||||||
|
- No silent unlinking occurs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Phase 2.4 – Manual Ticket Resolution (Backupchecks → Autotask)
|
||||||
|
|
||||||
|
### 7.1 Preconditions
|
||||||
|
|
||||||
|
The Resolve action is available only if:
|
||||||
|
|
||||||
|
- Autotask integration is enabled
|
||||||
|
- A valid Autotask Ticket ID exists
|
||||||
|
- Ticket is not already closed in PSA
|
||||||
|
|
||||||
|
### 7.2 Resolution flow
|
||||||
|
|
||||||
|
1. Operator triggers **Resolve ticket**
|
||||||
|
2. Backupchecks retrieves ticket details from Autotask
|
||||||
|
3. Time entry check is executed
|
||||||
|
|
||||||
|
#### Case A – No time entries
|
||||||
|
|
||||||
|
- Ticket is set to completed in Autotask
|
||||||
|
- Run is marked as **Resolved**
|
||||||
|
- Resolution origin:
|
||||||
|
- `Resolved by Backupchecks`
|
||||||
|
|
||||||
|
#### Case B – Time entries exist
|
||||||
|
|
||||||
|
- Ticket is not closed
|
||||||
|
- Fixed-format internal system note is added
|
||||||
|
- Run remains unresolved
|
||||||
|
|
||||||
|
### 7.3 Control moment – Phase 2.4
|
||||||
|
|
||||||
|
Functional validation:
|
||||||
|
|
||||||
|
- Resolution works only under defined rules
|
||||||
|
- Time entry logic is respected
|
||||||
|
- Resolution origin is persisted correctly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6.2 Resolution flow
|
||||||
|
|
||||||
|
1. Operator triggers **Resolve ticket**
|
||||||
|
2. Backupchecks retrieves ticket details from Autotask
|
||||||
|
3. Time entry check is executed
|
||||||
|
|
||||||
|
#### Case A – No time entries
|
||||||
|
|
||||||
|
- Ticket is set to the configured completed status in Autotask
|
||||||
|
- Run is marked as **Resolved**
|
||||||
|
- Resolution origin:
|
||||||
|
- `Resolved by Backupchecks`
|
||||||
|
|
||||||
|
#### Case B – Time entries exist
|
||||||
|
|
||||||
|
- Ticket is not closed
|
||||||
|
- A fixed-format internal system note is added
|
||||||
|
- Run remains unresolved
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6.3 Post-resolution sync alignment
|
||||||
|
|
||||||
|
If a ticket resolved by Backupchecks is later polled as Completed:
|
||||||
|
|
||||||
|
- Backupchecks keeps the existing resolved state
|
||||||
|
- Resolution origin remains:
|
||||||
|
- `Resolved by Backupchecks`
|
||||||
|
|
||||||
|
If Autotask resolves the ticket first:
|
||||||
|
|
||||||
|
- Backupchecks sets:
|
||||||
|
- `Resolved by PSA`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Data Model Adjustments
|
||||||
|
|
||||||
|
Phase 2 requires the following additional fields per run:
|
||||||
|
|
||||||
|
- `ticket_last_polled_at`
|
||||||
|
- `ticket_resolution_origin`:
|
||||||
|
- `psa`
|
||||||
|
- `backupchecks`
|
||||||
|
- `ticket_deleted_in_psa` (boolean)
|
||||||
|
|
||||||
|
Existing Phase 1 fields remain unchanged.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. UI Behaviour
|
||||||
|
|
||||||
|
### 8.1 Run Checks
|
||||||
|
|
||||||
|
- Visual indicator for linked tickets
|
||||||
|
- Resolution origin badge:
|
||||||
|
- Resolved by PSA
|
||||||
|
- Resolved by Backupchecks
|
||||||
|
- Warning banner for deleted tickets
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8.2 Job Details
|
||||||
|
|
||||||
|
- Same indicators as Run Checks
|
||||||
|
- Resolve action visibility follows resolution rules
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Error Handling & Logging
|
||||||
|
|
||||||
|
- All Autotask API calls are logged with:
|
||||||
|
- Correlation ID
|
||||||
|
- Ticket ID
|
||||||
|
- Run ID
|
||||||
|
|
||||||
|
- Polling failures do not block page rendering
|
||||||
|
- Partial polling failures are tolerated per ticket
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Explicit Non-Implementation (Phase 2)
|
||||||
|
|
||||||
|
The following are **not implemented**:
|
||||||
|
|
||||||
|
- Background schedulers
|
||||||
|
- Automatic ticket creation
|
||||||
|
- Automatic ticket reopening
|
||||||
|
- Status pushing without operator action
|
||||||
|
- Ticket content mutation beyond fixed system notes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Phase-based Implementation Workflow
|
||||||
|
|
||||||
|
For each Phase 2 sub-phase, a **dedicated chat** must be used. This ensures focus, traceability, and controlled implementation.
|
||||||
|
|
||||||
|
The instructions below must be copied **verbatim** when starting a new chat for the corresponding phase.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2.1 – Chat Instructions
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
Implement **read-only ticket polling** when the Run Checks page is opened.
|
||||||
|
|
||||||
|
Chat instructions:
|
||||||
|
|
||||||
|
- Scope is limited to Phase 2.1 only
|
||||||
|
- No ticket state changes are allowed
|
||||||
|
- No resolve, close, or mutation logic may be added
|
||||||
|
- Only backend polling and UI visibility updates are permitted
|
||||||
|
- All changes must be functionally testable on Run Checks
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- Polling executes only when Run Checks is opened
|
||||||
|
- Only relevant active tickets are queried
|
||||||
|
- No Backupchecks run state is modified
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2.2 – Chat Instructions
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
Implement **PSA-driven resolution handling** based on polling results.
|
||||||
|
|
||||||
|
Chat instructions:
|
||||||
|
|
||||||
|
- Phase 2.1 must already be completed and validated
|
||||||
|
- Only Completed ticket handling may be added
|
||||||
|
- Resolution origin must be stored and displayed
|
||||||
|
- No Backupchecks-initiated ticket changes are allowed
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- Completed tickets resolve runs correctly
|
||||||
|
- Resolution origin is accurate and persistent
|
||||||
|
- Active tickets remain unchanged
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2.3 – Chat Instructions
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
Implement **deleted ticket detection and operator visibility**.
|
||||||
|
|
||||||
|
Chat instructions:
|
||||||
|
|
||||||
|
- Phase 2.1 and 2.2 must already be completed
|
||||||
|
- Detection must be explicit and non-destructive
|
||||||
|
- Historical ticket references must remain intact
|
||||||
|
- UI must clearly indicate deleted state
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- Deleted tickets are reliably detected
|
||||||
|
- Operators are clearly informed
|
||||||
|
- Ticket actions are blocked correctly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2.4 – Chat Instructions
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
Implement **manual ticket resolution from Backupchecks to Autotask**.
|
||||||
|
|
||||||
|
Chat instructions:
|
||||||
|
|
||||||
|
- All previous Phase 2 sub-phases must be completed
|
||||||
|
- Resolution must be strictly operator-driven
|
||||||
|
- Time entry rules must be enforced exactly
|
||||||
|
- Resolution origin must be stored correctly
|
||||||
|
|
||||||
|
Exit criteria:
|
||||||
|
|
||||||
|
- Tickets are resolved only when allowed
|
||||||
|
- Time entry edge cases behave correctly
|
||||||
|
- State remains consistent with PSA polling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Phase 2 Completion Criteria
|
||||||
|
|
||||||
|
Phase 2 is considered complete when:
|
||||||
|
|
||||||
|
- All Phase 2 sub-phases have passed their control moments
|
||||||
|
- No cross-phase leakage of logic exists
|
||||||
|
- Enable/disable integration behaviour is respected
|
||||||
|
- Functional behaviour matches the approved design exactly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
End of Phase 2 implementation document.
|
||||||
|
|
||||||
@ -152,6 +152,17 @@ Changes:
|
|||||||
- Added proper ticket association to Job Details, matching the behaviour of manually entered tickets.
|
- Added proper ticket association to Job Details, matching the behaviour of manually entered tickets.
|
||||||
- Updated the Run Checks view to show the ticket indicator when an Autotask ticket is linked to a run.
|
- Updated the Run Checks view to show the ticket indicator when an Autotask ticket is linked to a run.
|
||||||
|
|
||||||
|
## v20260116-04-runchecks-autotask-ticket-polling
|
||||||
|
|
||||||
|
### Changes:
|
||||||
|
- Added read-only Autotask ticket polling triggered on Run Checks page load
|
||||||
|
- Introduced backend endpoint to poll only relevant active Autotask tickets linked to visible runs
|
||||||
|
- Implemented ticket ID deduplication to minimize Autotask API calls
|
||||||
|
- Ensured polling is best-effort and does not block Run Checks rendering
|
||||||
|
- Added client support for bulk ticket queries with per-ticket fallback
|
||||||
|
- Updated Run Checks UI to display polled PSA ticket status without modifying run state
|
||||||
|
- Explicitly prevented any ticket mutation, resolution, or Backupchecks state changes
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
## v0.1.21
|
## v0.1.21
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user