feat(bugtracker, tokens): Add project search, push_id, target_agent, tags, edit modal, extended severities (idea, wishlist), token actions (copy/toggle/delete), and agent integration guide

This commit is contained in:
Deploymentcenter Bot
2026-08-06 21:53:30 +02:00
parent cb52298c7a
commit c437bdee6f
9 changed files with 682 additions and 177 deletions
+9
View File
@@ -206,6 +206,15 @@ class TokenManager
return $stmt->execute([':id' => $tokenId]);
}
/**
* Permanently delete a Token (Master or Sub). If Master, child Sub-Tokens are deleted via cascade.
*/
public function deleteToken(string $tokenId): bool
{
$stmt = $this->db->prepare('DELETE FROM dc_tokens WHERE token_id = :id OR parent_token_id = :id');
return $stmt->execute([':id' => $tokenId]);
}
/**
* Get all Master Tokens with child count.
*/
+131 -26
View File
@@ -14,7 +14,7 @@ class BugRepo
}
/**
* Ingest a Bug or Feature Request. Automates error-hash deduplication for bugs.
* Ingest a Bug or Feature Request / Idea. Automates error-hash deduplication for bugs.
*/
public function reportItem(array $data): array
{
@@ -28,9 +28,12 @@ class BugRepo
$environment = in_array($data['environment'] ?? '', ['production', 'development', 'staging', 'testing'])
? $data['environment']
: 'production';
$severity = in_array($data['severity'] ?? '', ['low', 'medium', 'high', 'critical'])
$severity = in_array($data['severity'] ?? '', ['idea', 'wishlist', 'low', 'medium', 'high', 'critical'])
? $data['severity']
: 'medium';
: ($type === 'feature_request' ? 'medium' : 'medium');
$pushId = !empty($data['push_id']) ? trim($data['push_id']) : null;
$targetAgent = !empty($data['target_agent']) ? trim($data['target_agent']) : null;
$tags = !empty($data['tags']) ? trim($data['tags']) : null;
$createdBy = !empty($data['created_by']) ? trim($data['created_by']) : 'agent';
// Find associated project_id from dc_projects
@@ -59,10 +62,16 @@ class BugRepo
$newCount = (int)$existing['occurrence_count'] + 1;
$upd = $this->db->prepare('
UPDATE bugtracker_items
SET occurrence_count = :count, last_seen_at = NOW()
SET occurrence_count = :count,
last_seen_at = NOW(),
push_id = COALESCE(:push_id, push_id)
WHERE id = :id
');
$upd->execute([':count' => $newCount, ':id' => $existing['id']]);
$upd->execute([
':count' => $newCount,
':push_id' => $pushId,
':id' => $existing['id'],
]);
return [
'id' => (int)$existing['id'],
@@ -71,6 +80,7 @@ class BugRepo
'error_hash' => $errorHash,
'type' => $type,
'environment' => $environment,
'push_id' => $pushId,
];
}
}
@@ -80,39 +90,43 @@ class BugRepo
project_id, project_slug, type, title, description,
error_message, stack_trace, error_hash, build_version,
environment, severity, status, occurrence_count,
push_id, target_agent, tags,
first_seen_at, last_seen_at, created_by, created_at
) VALUES (
:pid, :slug, :type, :title, :desc,
:err, :trace, :hash, :build,
:env, :sev, "open", 1,
:push_id, :target_agent, :tags,
NOW(), NOW(), :created_by, NOW()
)
');
$ins->execute([
':pid' => $projectId,
':slug' => $projectSlug,
':type' => $type,
':title' => $title,
':desc' => $description,
':err' => $errorMessage,
':trace' => $stackTrace,
':hash' => $errorHash,
':build' => $buildVersion,
':env' => $environment,
':sev' => $severity,
':created_by' => $createdBy,
':pid' => $projectId,
':slug' => $projectSlug,
':type' => $type,
':title' => $title,
':desc' => $description,
':err' => $errorMessage,
':trace' => $stackTrace,
':hash' => $errorHash,
':build' => $buildVersion,
':env' => $environment,
':sev' => $severity,
':push_id' => $pushId,
':target_agent' => $targetAgent,
':tags' => $tags,
':created_by' => $createdBy,
]);
$newItemId = (int)$this->db->lastInsertId();
// Initial comment log
$this->addComment(
$newItemId,
$createdBy,
$type === 'bug' ? 'Bug in System erfasst.' : 'Feature-Request eingereicht.',
'reported'
);
$initialNote = ($type === 'bug')
? 'Bug in System erfasst.'
: ($severity === 'idea' ? '💡 Neue Idee / Gedanke hinterlegt.' : 'Feature-Request eingereicht.');
$this->addComment($newItemId, $createdBy, $initialNote, 'reported');
return [
'id' => $newItemId,
@@ -121,11 +135,12 @@ class BugRepo
'error_hash' => $errorHash,
'type' => $type,
'environment' => $environment,
'push_id' => $pushId,
];
}
/**
* Get filtered list of Bugs & Feature Requests.
* Get filtered list of Bugs, Features, & Ideas.
*/
public function getItems(array $filters = []): array
{
@@ -157,8 +172,18 @@ class BugRepo
$params[':severity'] = $filters['severity'];
}
if (!empty($filters['push_id'])) {
$where[] = 'push_id = :push_id';
$params[':push_id'] = trim($filters['push_id']);
}
if (!empty($filters['target_agent'])) {
$where[] = 'target_agent = :agent';
$params[':agent'] = trim($filters['target_agent']);
}
if (!empty($filters['search'])) {
$where[] = '(title LIKE :q OR description LIKE :q OR error_message LIKE :q)';
$where[] = '(title LIKE :q OR description LIKE :q OR error_message LIKE :q OR push_id LIKE :q OR target_agent LIKE :q OR tags LIKE :q)';
$params[':q'] = '%' . trim($filters['search']) . '%';
}
@@ -248,6 +273,83 @@ class BugRepo
return $result;
}
/**
* Comprehensive Item Edit (Status, Severity, Push ID, Target Agent, Tags, Notes).
*/
public function updateItemDetails(int $itemId, array $updates, string $author = 'agent'): bool
{
$existing = $this->getItemDetails($itemId);
if (!$existing) {
return false;
}
$fields = [];
$params = [':id' => $itemId];
$changes = [];
if (isset($updates['status']) && in_array($updates['status'], ['open', 'planned', 'in_progress', 'resolved', 'closed', 'rejected'])) {
$fields[] = 'status = :status';
$params[':status'] = $updates['status'];
if ($existing['status'] !== $updates['status']) {
$changes[] = "Status: {$existing['status']}{$updates['status']}";
}
}
if (isset($updates['severity']) && in_array($updates['severity'], ['idea', 'wishlist', 'low', 'medium', 'high', 'critical'])) {
$fields[] = 'severity = :severity';
$params[':severity'] = $updates['severity'];
if ($existing['severity'] !== $updates['severity']) {
$changes[] = "Schweregrad: {$existing['severity']}{$updates['severity']}";
}
}
if (array_key_exists('push_id', $updates)) {
$fields[] = 'push_id = :push_id';
$params[':push_id'] = !empty($updates['push_id']) ? trim($updates['push_id']) : null;
if ($existing['push_id'] !== $updates['push_id']) {
$changes[] = "Push-ID hinterlegt: {$updates['push_id']}";
}
}
if (array_key_exists('target_agent', $updates)) {
$fields[] = 'target_agent = :target_agent';
$params[':target_agent'] = !empty($updates['target_agent']) ? trim($updates['target_agent']) : null;
if ($existing['target_agent'] !== $updates['target_agent']) {
$changes[] = "Ziel-Agent: {$updates['target_agent']}";
}
}
if (array_key_exists('tags', $updates)) {
$fields[] = 'tags = :tags';
$params[':tags'] = !empty($updates['tags']) ? trim($updates['tags']) : null;
}
if (array_key_exists('resolved_in_build', $updates)) {
$fields[] = 'resolved_in_build = :build';
$params[':build'] = !empty($updates['resolved_in_build']) ? trim($updates['resolved_in_build']) : null;
}
if (array_key_exists('resolution_notes', $updates)) {
$fields[] = 'resolution_notes = :notes';
$params[':notes'] = !empty($updates['resolution_notes']) ? trim($updates['resolution_notes']) : null;
}
if (empty($fields)) {
return true;
}
$sql = 'UPDATE bugtracker_items SET ' . implode(', ', $fields) . ' WHERE id = :id';
$stmt = $this->db->prepare($sql);
$result = $stmt->execute($params);
if ($result && !empty($changes)) {
$changeMsg = 'Item aktualisiert: ' . implode(' | ', $changes);
$this->addComment($itemId, $author, $changeMsg, 'item_updated');
}
return $result;
}
/**
* Resolve a Bug or complete a Feature Request with build details.
*/
@@ -288,6 +390,7 @@ class BugRepo
'open_bugs_prod' => 0,
'open_bugs_dev' => 0,
'open_features' => 0,
'ideas_count' => 0,
'resolved_total' => 0,
'critical_bugs' => 0,
];
@@ -296,7 +399,8 @@ class BugRepo
SELECT
SUM(CASE WHEN type = "bug" AND environment = "production" AND status IN ("open", "in_progress") THEN 1 ELSE 0 END) as open_bugs_prod,
SUM(CASE WHEN type = "bug" AND environment = "development" AND status IN ("open", "in_progress") THEN 1 ELSE 0 END) as open_bugs_dev,
SUM(CASE WHEN type = "feature_request" AND status IN ("open", "planned", "in_progress") THEN 1 ELSE 0 END) as open_features,
SUM(CASE WHEN type = "feature_request" AND severity NOT IN ("idea") AND status IN ("open", "planned", "in_progress") THEN 1 ELSE 0 END) as open_features,
SUM(CASE WHEN severity = "idea" AND status IN ("open", "planned") THEN 1 ELSE 0 END) as ideas_count,
SUM(CASE WHEN status = "resolved" THEN 1 ELSE 0 END) as resolved_total,
SUM(CASE WHEN type = "bug" AND severity = "critical" AND status IN ("open", "in_progress") THEN 1 ELSE 0 END) as critical_bugs
FROM bugtracker_items
@@ -306,6 +410,7 @@ class BugRepo
$stats['open_bugs_prod'] = (int)($res['open_bugs_prod'] ?? 0);
$stats['open_bugs_dev'] = (int)($res['open_bugs_dev'] ?? 0);
$stats['open_features'] = (int)($res['open_features'] ?? 0);
$stats['ideas_count'] = (int)($res['ideas_count'] ?? 0);
$stats['resolved_total'] = (int)($res['resolved_total'] ?? 0);
$stats['critical_bugs'] = (int)($res['critical_bugs'] ?? 0);
}