From ce8862014f69b1c28d9288f8dfe42be8892204bc Mon Sep 17 00:00:00 2001 From: fds2610-bot Date: Tue, 4 Aug 2026 11:07:56 +0200 Subject: [PATCH] Farbe verwaister Tickets zuruecksetzen statt Projektfarbe zu behalten MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wenn ein Projekt im Projekte-Modal entfernt oder umbenannt wurde, hat CodebergSyncService dessen Tickets nicht mehr angefasst - sie blieben also dauerhaft in der alten Projektfarbe eingefaerbt, obwohl es dafuer gar kein Projekt mehr gab (Issue #34). sync() setzt die Farbe solcher Tickets nun nach jedem Lauf auf NULL zurueck, wodurch das Kanban die Standardfarbe statt der veralteten Projektfarbe zeigt. Schließt #34 --- api/src/Repository/TicketRepository.php | 27 +++++++++++++++++ api/src/Sync/CodebergSyncService.php | 12 +++++++- .../Unit/Repository/TicketRepositoryTest.php | 29 +++++++++++++++++++ .../Unit/Sync/CodebergSyncServiceTest.php | 20 +++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/api/src/Repository/TicketRepository.php b/api/src/Repository/TicketRepository.php index 011ce94..07b0c74 100644 --- a/api/src/Repository/TicketRepository.php +++ b/api/src/Repository/TicketRepository.php @@ -170,6 +170,33 @@ final class TicketRepository ]); } + /** + * Loescht die Projektfarbe von Tickets, deren Quell-Projekt nicht (mehr) in $slugs steht + * (Issue #34: nach Entfernen/Umbenennen eines Projekts im Projekte-Modal blieben dessen + * Tickets dauerhaft in dessen alter Farbe eingefaerbt, weil CodebergSyncService sie ab + * dann gar nicht mehr anfasst). Tickets ohne Projektbezug (repo = NULL, z.B. per API + * angelegt) bleiben unberuehrt. + * + * @param string[] $slugs - aktuell konfigurierte "owner/repo"-Werte. + */ + public function clearColorForReposNotIn(array $slugs): void + { + if ($slugs === []) { + $this->db->pdo()->exec( + "UPDATE tickets SET color = NULL WHERE repo IS NOT NULL AND color IS NOT NULL" + ); + + return; + } + + $placeholders = implode(', ', array_fill(0, count($slugs), '?')); + $stmt = $this->db->pdo()->prepare( + "UPDATE tickets SET color = NULL + WHERE repo IS NOT NULL AND color IS NOT NULL AND repo NOT IN ($placeholders)" + ); + $stmt->execute($slugs); + } + /** * Billiger "Fingerabdruck" (Anzahl + juengster Aenderungszeitpunkt) fuer den SSE-Endpoint * (Realtime\ChangeFeed, F13) - aendert sich bei jedem Anlegen/Verschieben eines Tickets. diff --git a/api/src/Sync/CodebergSyncService.php b/api/src/Sync/CodebergSyncService.php index f47ca2d..201c459 100644 --- a/api/src/Sync/CodebergSyncService.php +++ b/api/src/Sync/CodebergSyncService.php @@ -42,9 +42,19 @@ final class CodebergSyncService */ public function sync(): void { - foreach ($this->repoConfigs->all() as $repoConfig) { + $repoConfigs = $this->repoConfigs->all(); + + foreach ($repoConfigs as $repoConfig) { $this->syncRepo($repoConfig['owner'], $repoConfig['repo'], $repoConfig['color']); } + + // Issue #34: Tickets aus zwischenzeitlich entfernten oder umbenannten Projekten werden + // oben nicht mehr angefasst und wuerden sonst dauerhaft in der alten, nicht mehr + // zutreffenden Projektfarbe angezeigt. + $this->tickets->clearColorForReposNotIn(array_map( + static fn (array $repoConfig): string => $repoConfig['owner'] . '/' . $repoConfig['repo'], + $repoConfigs + )); } private function syncRepo(string $owner, string $repo, string $color): void diff --git a/api/tests/Unit/Repository/TicketRepositoryTest.php b/api/tests/Unit/Repository/TicketRepositoryTest.php index 2c16673..f92bf9f 100644 --- a/api/tests/Unit/Repository/TicketRepositoryTest.php +++ b/api/tests/Unit/Repository/TicketRepositoryTest.php @@ -164,4 +164,33 @@ final class TicketRepositoryTest extends TestCase self::assertSame($this->repository->fingerprint(), $this->repository->fingerprint()); } + + public function testClearColorForReposNotInRemovesColorFromOrphanedRepo(): void + { + $kept = $this->repository->create(['title' => 'A', 'repo' => 'fds2610/monitor', 'color' => '#fbca04']); + $orphaned = $this->repository->create(['title' => 'B', 'repo' => 'fds2610/altes-projekt', 'color' => '#3b82f6']); + + $this->repository->clearColorForReposNotIn(['fds2610/monitor']); + + self::assertSame('#fbca04', $this->repository->find((int) $kept['id'])['color']); + self::assertNull($this->repository->find((int) $orphaned['id'])['color']); + } + + public function testClearColorForReposNotInIgnoresTicketsWithoutRepo(): void + { + $manual = $this->repository->create(['title' => 'API-Ticket', 'color' => '#ffffff']); + + $this->repository->clearColorForReposNotIn(['fds2610/monitor']); + + self::assertSame('#ffffff', $this->repository->find((int) $manual['id'])['color']); + } + + public function testClearColorForReposNotInClearsAllColorsWhenNoRepoConfigured(): void + { + $ticket = $this->repository->create(['title' => 'B', 'repo' => 'fds2610/altes-projekt', 'color' => '#3b82f6']); + + $this->repository->clearColorForReposNotIn([]); + + self::assertNull($this->repository->find((int) $ticket['id'])['color']); + } } diff --git a/api/tests/Unit/Sync/CodebergSyncServiceTest.php b/api/tests/Unit/Sync/CodebergSyncServiceTest.php index 265bb86..8544c61 100644 --- a/api/tests/Unit/Sync/CodebergSyncServiceTest.php +++ b/api/tests/Unit/Sync/CodebergSyncServiceTest.php @@ -117,6 +117,26 @@ final class CodebergSyncServiceTest extends TestCase self::assertSame('#28a745', $steuerkramTicket['color']); } + public function testSyncClearsColorOfTicketsFromRemovedProject(): void + { + $tickets = new TicketRepository(Database::inMemory()); + $client = self::clientForMonitor([ + 'open' => [['number' => 1, 'title' => 'Issue', 'body' => null, 'labels' => []]], + 'closed' => [], + ]); + + (new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO])))->sync(); + self::assertSame('#fbca04', $tickets->findByExternalRef('1', self::MONITOR_SLUG)['color']); + + // Projekt wurde im Projekte-Modal entfernt (Issue #34): der naechste Sync-Lauf sieht + // es nicht mehr in repoConfigsFor() und darf das verwaiste Ticket nicht laenger blau + // faerben, obwohl es kein Projekt mit dieser Farbe mehr gibt. + $client = new FakeCodebergClient([]); + (new CodebergSyncService($client, $tickets, self::repoConfigsFor([])))->sync(); + + self::assertNull($tickets->findByExternalRef('1', self::MONITOR_SLUG)['color']); + } + public function testSyncDoesNotResetManuallyMovedTicket(): void { $tickets = new TicketRepository(Database::inMemory());