From bbd2317debe76bfb9e2e3796286492f476fbbe85 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 24 Nov 2024 13:23:23 +0100 Subject: [PATCH] Add graphs to the dashboard --- appinfo/routes.php | 4 + lib/Controller/DashboardController.php | 126 ++- lib/Db/CallLogMapper.php | 70 ++ lib/Db/JobLogMapper.php | 78 ++ lib/Db/SynchronizationContractLogMapper.php | 62 ++ src/views/dashboard/DashboardIndex.vue | 944 ++++++++++++++++++-- 6 files changed, 1178 insertions(+), 106 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index f96805d7..b78e7414 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -11,6 +11,10 @@ ], 'routes' => [ ['name' => 'dashboard#page', 'url' => '/', 'verb' => 'GET'], + ['name' => 'dashboard#index', 'url' => '/api/dashboard', 'verb' => 'GET'], + ['name' => 'dashboard#getCallStats', 'url' => '/api/dashboard/callstats', 'verb' => 'GET'], + ['name' => 'dashboard#getJobStats', 'url' => '/api/dashboard/jobstats', 'verb' => 'GET'], + ['name' => 'dashboard#getSyncStats', 'url' => '/api/dashboard/syncstats', 'verb' => 'GET'], ['name' => 'sources#test', 'url' => '/api/source-test/{id}', 'verb' => 'POST'], ['name' => 'sources#logs', 'url' => '/api/sources-logs/{id}', 'verb' => 'GET'], ['name' => 'jobs#run', 'url' => '/api/jobs-test/{id}', 'verb' => 'POST'], diff --git a/lib/Controller/DashboardController.php b/lib/Controller/DashboardController.php index a1979033..65666edf 100644 --- a/lib/Controller/DashboardController.php +++ b/lib/Controller/DashboardController.php @@ -14,36 +14,30 @@ use OCA\OpenConnector\Db\EndpointMapper; use OCA\OpenConnector\Db\JobMapper; use OCA\OpenConnector\Db\MappingMapper; +use OCA\OpenConnector\Db\CallLogMapper; +use OCA\OpenConnector\Db\JobLogMapper; +use OCA\OpenConnector\Db\SynchronizationContractLogMapper; +/** + * @package OCA\OpenConnector\Controller + */ class DashboardController extends Controller { - private $synchronizationMapper; - private $sourceMapper; - private $synchronizationContractMapper; - private $consumerMapper; - private $endpointMapper; - private $jobMapper; - private $mappingMapper; - public function __construct( $appName, IRequest $request, - SynchronizationMapper $synchronizationMapper, - SourceMapper $sourceMapper, - SynchronizationContractMapper $synchronizationContractMapper, - ConsumerMapper $consumerMapper, - EndpointMapper $endpointMapper, - JobMapper $jobMapper, - MappingMapper $mappingMapper + private readonly SynchronizationMapper $synchronizationMapper, + private readonly SourceMapper $sourceMapper, + private readonly SynchronizationContractMapper $synchronizationContractMapper, + private readonly ConsumerMapper $consumerMapper, + private readonly EndpointMapper $endpointMapper, + private readonly JobMapper $jobMapper, + private readonly MappingMapper $mappingMapper, + private readonly CallLogMapper $callLogMapper, + private readonly JobLogMapper $jobLogMapper, + private readonly SynchronizationContractLogMapper $synchronizationContractLogMapper ) { parent::__construct($appName, $request); - $this->synchronizationMapper = $synchronizationMapper; - $this->sourceMapper = $sourceMapper; - $this->synchronizationContractMapper = $synchronizationContractMapper; - $this->consumerMapper = $consumerMapper; - $this->endpointMapper = $endpointMapper; - $this->jobMapper = $jobMapper; - $this->mappingMapper = $mappingMapper; } /** @@ -82,17 +76,97 @@ public function index(): JSONResponse { try { $results = [ - "synchronizations" => $this->synchronizationMapper->getTotalCallCount(), "sources" => $this->sourceMapper->getTotalCallCount(), + "mappings" => $this->mappingMapper->getTotalCallCount(), + "synchronizations" => $this->synchronizationMapper->getTotalCallCount(), "synchronizationContracts" => $this->synchronizationContractMapper->getTotalCallCount(), - "consumers" => $this->consumerMapper->getTotalCallCount(), - "endpoints" => $this->endpointMapper->getTotalCallCount(), "jobs" => $this->jobMapper->getTotalCallCount(), - "mappings" => $this->mappingMapper->getTotalCallCount() + "endpoints" => $this->endpointMapper->getTotalCallCount() ]; return new JSONResponse($results); } catch (\Exception $e) { return new JSONResponse(['error' => $e->getMessage()], 500); } } + + /** + * Get call statistics for the dashboard + * + * @NoAdminRequired + * @NoCSRFRequired + * @param string|null $from Start date in ISO format + * @param string|null $to End date in ISO format + * @return JSONResponse + */ + public function getCallStats(?string $from = null, ?string $to = null): JSONResponse + { + try { + $fromDate = $from ? new \DateTime($from) : (new \DateTime())->modify('-7 days'); + $toDate = $to ? new \DateTime($to) : new \DateTime(); + + $dailyStats = $this->callLogMapper->getCallStatsByDateRange($fromDate, $toDate); + $hourlyStats = $this->callLogMapper->getCallStatsByHourRange($fromDate, $toDate); + + return new JSONResponse([ + 'daily' => $dailyStats, + 'hourly' => $hourlyStats + ]); + } catch (\Exception $e) { + return new JSONResponse(['error' => $e->getMessage()], 500); + } + } + + /** + * Get job statistics for the dashboard + * + * @NoAdminRequired + * @NoCSRFRequired + * @param string|null $from Start date in ISO format + * @param string|null $to End date in ISO format + * @return JSONResponse + */ + public function getJobStats(?string $from = null, ?string $to = null): JSONResponse + { + try { + $fromDate = $from ? new \DateTime($from) : (new \DateTime())->modify('-7 days'); + $toDate = $to ? new \DateTime($to) : new \DateTime(); + + $dailyStats = $this->jobLogMapper->getJobStatsByDateRange($fromDate, $toDate); + $hourlyStats = $this->jobLogMapper->getJobStatsByHourRange($fromDate, $toDate); + + return new JSONResponse([ + 'daily' => $dailyStats, + 'hourly' => $hourlyStats + ]); + } catch (\Exception $e) { + return new JSONResponse(['error' => $e->getMessage()], 500); + } + } + + /** + * Get synchronization statistics for the dashboard + * + * @NoAdminRequired + * @NoCSRFRequired + * @param string|null $from Start date in ISO format + * @param string|null $to End date in ISO format + * @return JSONResponse + */ + public function getSyncStats(?string $from = null, ?string $to = null): JSONResponse + { + try { + $fromDate = $from ? new \DateTime($from) : (new \DateTime())->modify('-7 days'); + $toDate = $to ? new \DateTime($to) : new \DateTime(); + + $dailyStats = $this->synchronizationContractLogMapper->getSyncStatsByDateRange($fromDate, $toDate); + $hourlyStats = $this->synchronizationContractLogMapper->getSyncStatsByHourRange($fromDate, $toDate); + + return new JSONResponse([ + 'daily' => $dailyStats, + 'hourly' => $hourlyStats + ]); + } catch (\Exception $e) { + return new JSONResponse(['error' => $e->getMessage()], 500); + } + } } diff --git a/lib/Db/CallLogMapper.php b/lib/Db/CallLogMapper.php index 49cac829..d130542b 100644 --- a/lib/Db/CallLogMapper.php +++ b/lib/Db/CallLogMapper.php @@ -194,4 +194,74 @@ public function getLastCallLog(): ?CallLog return null; } } + + /** + * Get call statistics grouped by date for a specific date range + * + * @param \DateTime $from Start date + * @param \DateTime $to End date + * @return array Array of daily statistics with success and error counts + */ + public function getCallStatsByDateRange(\DateTime $from, \DateTime $to): array + { + $qb = $this->db->getQueryBuilder(); + + $qb->select( + $qb->createFunction('DATE(created) as date'), + $qb->createFunction('SUM(CASE WHEN status_code >= 200 AND status_code < 300 THEN 1 ELSE 0 END) as success'), + $qb->createFunction('SUM(CASE WHEN status_code < 200 OR status_code >= 300 THEN 1 ELSE 0 END) as error') + ) + ->from('openconnector_call_logs') + ->where($qb->expr()->gte('created', $qb->createNamedParameter($from->format('Y-m-d H:i:s')))) + ->andWhere($qb->expr()->lte('created', $qb->createNamedParameter($to->format('Y-m-d H:i:s')))) + ->groupBy('date') + ->orderBy('date', 'ASC'); + + $result = $qb->execute(); + $stats = []; + + while ($row = $result->fetch()) { + $stats[$row['date']] = [ + 'success' => (int)$row['success'], + 'error' => (int)$row['error'] + ]; + } + + return $stats; + } + + /** + * Get call statistics grouped by hour for a specific date range + * + * @param \DateTime $from Start date + * @param \DateTime $to End date + * @return array Array of hourly statistics with success and error counts + */ + public function getCallStatsByHourRange(\DateTime $from, \DateTime $to): array + { + $qb = $this->db->getQueryBuilder(); + + $qb->select( + $qb->createFunction('HOUR(created) as hour'), + $qb->createFunction('SUM(CASE WHEN status_code >= 200 AND status_code < 300 THEN 1 ELSE 0 END) as success'), + $qb->createFunction('SUM(CASE WHEN status_code < 200 OR status_code >= 300 THEN 1 ELSE 0 END) as error') + ) + ->from('openconnector_call_logs') + ->where($qb->expr()->gte('created', $qb->createNamedParameter($from->format('Y-m-d H:i:s')))) + ->andWhere($qb->expr()->lte('created', $qb->createNamedParameter($to->format('Y-m-d H:i:s')))) + ->groupBy('hour') + ->orderBy('hour', 'ASC'); + + $result = $qb->execute(); + $stats = []; + + while ($row = $result->fetch()) { + $stats[$row['hour']] = [ + 'success' => (int)$row['success'], + 'error' => (int)$row['error'] + ]; + } + + return $stats; + } } diff --git a/lib/Db/JobLogMapper.php b/lib/Db/JobLogMapper.php index cc0dc683..13c6f3ce 100644 --- a/lib/Db/JobLogMapper.php +++ b/lib/Db/JobLogMapper.php @@ -98,4 +98,82 @@ public function getLastCallLog(): ?JobLog return null; } } + + /** + * Get job statistics grouped by date for a specific date range + * + * @param \DateTime $from Start date + * @param \DateTime $to End date + * @return array Array of daily statistics with counts per log level + */ + public function getJobStatsByDateRange(\DateTime $from, \DateTime $to): array + { + $qb = $this->db->getQueryBuilder(); + + $qb->select( + $qb->createFunction('DATE(created) as date'), + $qb->createFunction('SUM(CASE WHEN level = \'INFO\' THEN 1 ELSE 0 END) as info'), + $qb->createFunction('SUM(CASE WHEN level = \'WARNING\' THEN 1 ELSE 0 END) as warning'), + $qb->createFunction('SUM(CASE WHEN level = \'ERROR\' THEN 1 ELSE 0 END) as error'), + $qb->createFunction('SUM(CASE WHEN level = \'DEBUG\' THEN 1 ELSE 0 END) as debug') + ) + ->from('openconnector_job_logs') + ->where($qb->expr()->gte('created', $qb->createNamedParameter($from->format('Y-m-d H:i:s')))) + ->andWhere($qb->expr()->lte('created', $qb->createNamedParameter($to->format('Y-m-d H:i:s')))) + ->groupBy('date') + ->orderBy('date', 'ASC'); + + $result = $qb->execute(); + $stats = []; + + while ($row = $result->fetch()) { + $stats[$row['date']] = [ + 'info' => (int)$row['info'], + 'warning' => (int)$row['warning'], + 'error' => (int)$row['error'], + 'debug' => (int)$row['debug'] + ]; + } + + return $stats; + } + + /** + * Get job statistics grouped by hour for a specific date range + * + * @param \DateTime $from Start date + * @param \DateTime $to End date + * @return array Array of hourly statistics with counts per log level + */ + public function getJobStatsByHourRange(\DateTime $from, \DateTime $to): array + { + $qb = $this->db->getQueryBuilder(); + + $qb->select( + $qb->createFunction('HOUR(created) as hour'), + $qb->createFunction('SUM(CASE WHEN level = \'INFO\' THEN 1 ELSE 0 END) as info'), + $qb->createFunction('SUM(CASE WHEN level = \'WARNING\' THEN 1 ELSE 0 END) as warning'), + $qb->createFunction('SUM(CASE WHEN level = \'ERROR\' THEN 1 ELSE 0 END) as error'), + $qb->createFunction('SUM(CASE WHEN level = \'DEBUG\' THEN 1 ELSE 0 END) as debug') + ) + ->from('openconnector_job_logs') + ->where($qb->expr()->gte('created', $qb->createNamedParameter($from->format('Y-m-d H:i:s')))) + ->andWhere($qb->expr()->lte('created', $qb->createNamedParameter($to->format('Y-m-d H:i:s')))) + ->groupBy('hour') + ->orderBy('hour', 'ASC'); + + $result = $qb->execute(); + $stats = []; + + while ($row = $result->fetch()) { + $stats[$row['hour']] = [ + 'info' => (int)$row['info'], + 'warning' => (int)$row['warning'], + 'error' => (int)$row['error'], + 'debug' => (int)$row['debug'] + ]; + } + + return $stats; + } } diff --git a/lib/Db/SynchronizationContractLogMapper.php b/lib/Db/SynchronizationContractLogMapper.php index 2fa1779c..4fb7c71a 100644 --- a/lib/Db/SynchronizationContractLogMapper.php +++ b/lib/Db/SynchronizationContractLogMapper.php @@ -93,4 +93,66 @@ public function updateFromArray(int $id, array $object): SynchronizationContract return $this->update($obj); } + + /** + * Get synchronization execution counts by date for a specific date range + * + * @param \DateTime $from Start date + * @param \DateTime $to End date + * @return array Array of daily execution counts + */ + public function getSyncStatsByDateRange(\DateTime $from, \DateTime $to): array + { + $qb = $this->db->getQueryBuilder(); + + $qb->select( + $qb->createFunction('DATE(created) as date'), + $qb->createFunction('COUNT(*) as executions') + ) + ->from('openconnector_synchronization_contract_logs') + ->where($qb->expr()->gte('created', $qb->createNamedParameter($from->format('Y-m-d H:i:s')))) + ->andWhere($qb->expr()->lte('created', $qb->createNamedParameter($to->format('Y-m-d H:i:s')))) + ->groupBy('date') + ->orderBy('date', 'ASC'); + + $result = $qb->execute(); + $stats = []; + + while ($row = $result->fetch()) { + $stats[$row['date']] = (int)$row['executions']; + } + + return $stats; + } + + /** + * Get synchronization execution counts by hour for a specific date range + * + * @param \DateTime $from Start date + * @param \DateTime $to End date + * @return array Array of hourly execution counts + */ + public function getSyncStatsByHourRange(\DateTime $from, \DateTime $to): array + { + $qb = $this->db->getQueryBuilder(); + + $qb->select( + $qb->createFunction('HOUR(created) as hour'), + $qb->createFunction('COUNT(*) as executions') + ) + ->from('openconnector_synchronization_contract_logs') + ->where($qb->expr()->gte('created', $qb->createNamedParameter($from->format('Y-m-d H:i:s')))) + ->andWhere($qb->expr()->lte('created', $qb->createNamedParameter($to->format('Y-m-d H:i:s')))) + ->groupBy('hour') + ->orderBy('hour', 'ASC'); + + $result = $qb->execute(); + $stats = []; + + while ($row = $result->fetch()) { + $stats[$row['hour']] = (int)$row['executions']; + } + + return $stats; + } } diff --git a/src/views/dashboard/DashboardIndex.vue b/src/views/dashboard/DashboardIndex.vue index 1e8a5dd8..bc187e89 100644 --- a/src/views/dashboard/DashboardIndex.vue +++ b/src/views/dashboard/DashboardIndex.vue @@ -1,3 +1,7 @@ + +