diff --git a/config/packages/Centreon.yaml b/config/packages/Centreon.yaml index 2fa70925c43..260499accda 100644 --- a/config/packages/Centreon.yaml +++ b/config/packages/Centreon.yaml @@ -53,6 +53,14 @@ services: class: Centreon\Infrastructure\Serializer\ObjectConstructor public: false + json_presenter: + class: Core\Infrastructure\Common\Presenter\JsonPresenter + public: false + + Core\Infrastructure\Common\Presenter\PresenterFormatterInterface: + class: Core\Infrastructure\Common\Presenter\JsonPresenter + public: false + # Encryption Security\Interfaces\EncryptionInterface: class: Security\Encryption @@ -85,6 +93,14 @@ services: class: Centreon\Domain\Contact\ContactProvider public: true + presenter.download.csv: + class: Core\Infrastructure\Common\Presenter\DownloadPresenter + arguments: ['@Core\Infrastructure\Common\Presenter\CsvPresenter'] + + Core\Application\RealTime\UseCase\FindPerformanceMetrics\FindPerformanceMetricPresenterInterface: + class: Core\Infrastructure\RealTime\Api\DownloadPerformanceMetrics\DownloadPerformanceMetricsPresenter + arguments: ['@presenter.download.csv'] + # Authentication Core\Security\Application\Repository\ReadAccessGroupRepositoryInterface: class: Core\Security\Infrastructure\Repository\DbReadAccessGroupRepository diff --git a/config/routes/Centreon/monitoring/metric.yaml b/config/routes/Centreon/monitoring/metric.yaml index ea6e0ddfff7..afd84ffff36 100644 --- a/config/routes/Centreon/monitoring/metric.yaml +++ b/config/routes/Centreon/monitoring/metric.yaml @@ -29,6 +29,15 @@ monitoring.metric.getServicePerformanceMetrics: controller: 'Centreon\Application\Controller\Monitoring\MetricController::getServicePerformanceMetrics' condition: "request.attributes.get('version') >= 21.10" +monitoring.metric.downloadPerformanceMetrics: + methods: GET + path: /monitoring/hosts/{hostId}/services/{serviceId}/metrics/download + requirements: + hostId: '\d+' + serviceId: '\d+' + controller: 'Core\Infrastructure\RealTime\Api\DownloadPerformanceMetrics\DownloadPerformanceMetricsController' + condition: "request.attributes.get('version') >= 22.10" + monitoring.metric.getServiceStatusMetrics: methods: GET path: /monitoring/hosts/{hostId}/services/{serviceId}/metrics/status diff --git a/doc/API/centreon-api-v22.10.yaml b/doc/API/centreon-api-v22.10.yaml index 671bc909cbd..47d8a780ac9 100644 --- a/doc/API/centreon-api-v22.10.yaml +++ b/doc/API/centreon-api-v22.10.yaml @@ -3400,6 +3400,40 @@ paths: $ref: '#/components/responses/NotFoundHostOrService' '500': $ref: '#/components/responses/InternalServerError' + /monitoring/hosts/{host_id}/services/{service_id}/metrics/download: + get: + tags: + - Metrics + summary: 'Download performance data as csv file' + parameters: + - $ref: '#/components/parameters/HostId' + - $ref: '#/components/parameters/ServiceId' + - in: query + name: start_date + required: true + description: "Start date of metrics (date format should be in ISO 8601)" + schema: + type: string + format: date-time + example: '2022-01-01T00:00:22Z' + - in: query + name: end + required: true + description: "End date of metrics (date format should be in ISO 8601)" + schema: + type: string + format: date-time + example: '2023-01-01T00:00:00Z' + responses: + '200': + description: A CSV file containg performance data + content: + application/force-download: + schema: + type: string + format: string + '500': + $ref: '#/components/responses/InternalServerError' /monitoring/hosts/{host_id}/services/{service_id}/metrics/status: get: tags: diff --git a/src/Core/Application/RealTime/Repository/ReadIndexDataRepositoryInterface.php b/src/Core/Application/RealTime/Repository/ReadIndexDataRepositoryInterface.php new file mode 100644 index 00000000000..f9b19161c3a --- /dev/null +++ b/src/Core/Application/RealTime/Repository/ReadIndexDataRepositoryInterface.php @@ -0,0 +1,43 @@ + + */ + public function findMetricsByIndexId(int $indexId): array; +} diff --git a/src/Core/Application/RealTime/Repository/ReadPerformanceDataRepositoryInterface.php b/src/Core/Application/RealTime/Repository/ReadPerformanceDataRepositoryInterface.php new file mode 100644 index 00000000000..3d844da3e29 --- /dev/null +++ b/src/Core/Application/RealTime/Repository/ReadPerformanceDataRepositoryInterface.php @@ -0,0 +1,43 @@ + $metrics + * @param DateTimeInterface $startDate + * @param DateTimeInterface $endDate + * @return iterable + */ + public function findDataByMetricsAndDates( + array $metrics, + DateTimeInterface $startDate, + DateTimeInterface $endDate + ): iterable; +} diff --git a/src/Core/Application/RealTime/UseCase/FindPerformanceMetrics/FindPerformanceMetricPresenterInterface.php b/src/Core/Application/RealTime/UseCase/FindPerformanceMetrics/FindPerformanceMetricPresenterInterface.php new file mode 100644 index 00000000000..4153fdc06b3 --- /dev/null +++ b/src/Core/Application/RealTime/UseCase/FindPerformanceMetrics/FindPerformanceMetricPresenterInterface.php @@ -0,0 +1,31 @@ + + */ + public iterable $performanceMetrics = []; + + /** + * @param iterable $performanceMetrics + */ + public function __construct(iterable $performanceMetrics) + { + $this->performanceMetrics = $this->performanceMetricToArray($performanceMetrics); + } + + /** + * @param iterable $performanceMetrics + * @return iterable + */ + private function performanceMetricToArray(iterable $performanceMetrics): iterable + { + foreach ($performanceMetrics as $performanceMetric) { + yield $this->formatPerformanceMetric($performanceMetric); + } + } + + /** + * @param PerformanceMetric $performanceMetric + * @return array + */ + private function formatPerformanceMetric(PerformanceMetric $performanceMetric): array + { + $formattedData = [ + 'time' => $performanceMetric->getDateValue()->getTimestamp(), + 'humantime' => $performanceMetric->getDateValue()->format('Y-m-d H:i:s') + ]; + + foreach ($performanceMetric->getMetricValues() as $metricValue) { + $formattedData[$metricValue->getName()] = sprintf('%f', $metricValue->getValue()); + } + + return $formattedData; + } +} diff --git a/src/Core/Application/RealTime/UseCase/FindPerformanceMetrics/FindPerformanceMetrics.php b/src/Core/Application/RealTime/UseCase/FindPerformanceMetrics/FindPerformanceMetrics.php new file mode 100644 index 00000000000..8798494830f --- /dev/null +++ b/src/Core/Application/RealTime/UseCase/FindPerformanceMetrics/FindPerformanceMetrics.php @@ -0,0 +1,116 @@ +debug( + 'Retrieve performance metrics', + [ + 'host_id' => $request->hostId, + 'service_id' => $request->serviceId + ] + ); + + $index = $this->indexDataRepository->findIndexByHostIdAndServiceId($request->hostId, $request->serviceId); + $metrics = $this->metricRepository->findMetricsByIndexId($index); + + $performanceMetrics = $this->performanceDataRepository->findDataByMetricsAndDates( + $metrics, + $request->startDate, + $request->endDate + ); + + $fileName = $this->generateDownloadFileNameByIndex($index); + $this->info('Filename used to download metrics', ['filename' => $fileName]); + $presenter->setDownloadFileName($fileName); + $presenter->present(new FindPerformanceMetricResponse($performanceMetrics)); + } catch (\Throwable $ex) { + $this->error( + 'Impossible to retrieve performance metrics', + [ + 'host_id' => $request->hostId, + 'service_id' => $request->serviceId, + 'error_message' => $ex->__toString(), + ] + ); + $presenter->setResponseStatus( + new ErrorResponse('Impossible to retrieve performance metrics') + ); + } + } + + /** + * @param int $index + * @return string + */ + private function generateDownloadFileNameByIndex(int $index): string + { + $indexData = $this->indexDataRepository->findHostNameAndServiceDescriptionByIndex($index); + + if (!$indexData instanceof IndexData) { + return (string) $index; + } + + $hostName = $indexData->getHostName(); + $serviceDescription = $indexData->getServiceDescription(); + + if ($hostName !== '' && $serviceDescription !== '') { + return sprintf('%s_%s', $hostName, $serviceDescription); + } + + return (string) $index; + } +} diff --git a/src/Core/Domain/RealTime/Model/IndexData.php b/src/Core/Domain/RealTime/Model/IndexData.php new file mode 100644 index 00000000000..fda49d045cd --- /dev/null +++ b/src/Core/Domain/RealTime/Model/IndexData.php @@ -0,0 +1,51 @@ +hostName; + } + + /** + * @return string + */ + public function getServiceDescription(): string + { + return $this->serviceDescription; + } +} diff --git a/src/Core/Domain/RealTime/Model/Metric.php b/src/Core/Domain/RealTime/Model/Metric.php new file mode 100644 index 00000000000..4c7931feba7 --- /dev/null +++ b/src/Core/Domain/RealTime/Model/Metric.php @@ -0,0 +1,51 @@ +id; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } +} diff --git a/src/Core/Domain/RealTime/Model/MetricValue.php b/src/Core/Domain/RealTime/Model/MetricValue.php new file mode 100644 index 00000000000..38894a4a536 --- /dev/null +++ b/src/Core/Domain/RealTime/Model/MetricValue.php @@ -0,0 +1,51 @@ +name; + } + + /** + * @return float + */ + public function getValue(): float + { + return $this->value; + } +} diff --git a/src/Core/Domain/RealTime/Model/PerformanceMetric.php b/src/Core/Domain/RealTime/Model/PerformanceMetric.php new file mode 100644 index 00000000000..c4bbfe5abf2 --- /dev/null +++ b/src/Core/Domain/RealTime/Model/PerformanceMetric.php @@ -0,0 +1,65 @@ +metricValues[] = $metricValue; + } + + /** + * @return MetricValue[] + */ + public function getMetricValues(): array + { + return $this->metricValues; + } + + /** + * @return DateTimeInterface + */ + public function getDateValue(): DateTimeInterface + { + return $this->dateValue; + } +} diff --git a/src/Core/Infrastructure/Common/Presenter/AbstractPresenter.php b/src/Core/Infrastructure/Common/Presenter/AbstractPresenter.php new file mode 100644 index 00000000000..59a82cd9bed --- /dev/null +++ b/src/Core/Infrastructure/Common/Presenter/AbstractPresenter.php @@ -0,0 +1,102 @@ + + */ + protected array $responseHeaders = []; + + /** + * @param array $responseHeaders + */ + public function setResponseHeaders(array $responseHeaders): void + { + $this->responseHeaders = $responseHeaders; + } + + /** + * @return array + */ + public function getResponseHeaders(): array + { + return $this->responseHeaders; + } + + /** + * Format content on error + * + * @param mixed $data + * @param integer $code + * @return mixed[]|null + */ + protected function formatErrorContent(mixed $data, int $code): ?array + { + $content = null; + + if (is_a($data, ResponseStatusInterface::class)) { + $content = [ + 'code' => $code, + 'message' => $data->getMessage(), + ]; + if (is_a($data, BodyResponseInterface::class)) { + $content = array_merge($content, $data->getBody()); + } + } + + return $content; + } + + /** + * Generates json response with error message and http code + * @param mixed $data + * @param int $code + * @return JsonResponse + */ + protected function generateJsonErrorResponse(mixed $data, int $code): JsonResponse + { + $errorData = $this->formatErrorContent($data, $code); + + return $this->generateJsonResponse($errorData, $code); + } + + /** + * @param mixed $data + * @param int $code + * @return JsonResponse + */ + protected function generateJsonResponse(mixed $data, int $code): JsonResponse + { + if (is_a($data, \Generator::class)) { + $data = iterator_to_array($data); + } + return new JsonResponse($data, $code, $this->responseHeaders); + } +} diff --git a/src/Core/Infrastructure/Common/Presenter/CsvPresenter.php b/src/Core/Infrastructure/Common/Presenter/CsvPresenter.php new file mode 100644 index 00000000000..3040aebc139 --- /dev/null +++ b/src/Core/Infrastructure/Common/Presenter/CsvPresenter.php @@ -0,0 +1,68 @@ +data = $data; + } + + /** + * @inheritDoc + */ + public function show(): Response + { + $response = new StreamedResponse(null, Response::HTTP_OK, $this->responseHeaders); + $response->setCallback(function () { + $handle = fopen('php://output', 'r+'); + if ($handle === false) { + throw new \RuntimeException('Unable to open the output buffer'); + } + $lineHeadersCreated = false; + foreach ($this->data as $data) { + if (! $lineHeadersCreated) { + $columnNames = array_keys($data); + fputcsv($handle, $columnNames, ';'); + $lineHeadersCreated = true; + } + $columnValues = array_values($data); + fputcsv($handle, $columnValues, ';'); + } + + fclose($handle); + }); + + return $response; + } +} diff --git a/src/Core/Infrastructure/Common/Presenter/DownloadInterface.php b/src/Core/Infrastructure/Common/Presenter/DownloadInterface.php new file mode 100644 index 00000000000..9c1d90755b0 --- /dev/null +++ b/src/Core/Infrastructure/Common/Presenter/DownloadInterface.php @@ -0,0 +1,29 @@ +presenter->present($data); + $originalHeaders = $this->presenter->getResponseHeaders(); + $originalHeaders['Content-Type'] = 'application/force-download'; + $originalHeaders['Content-Disposition'] = 'attachment; filename="' . $this->generateDownloadFileName() . '"'; + $this->presenter->setResponseHeaders($originalHeaders); + } + + /** + * @inheritDoc + */ + public function show(): Response + { + return $this->presenter->show(); + } + + /** + * @inheritDoc + */ + public function setDownloadFileName(string $fileName): void + { + $this->downloadFileName = $fileName; + } + + /** + * Generates download file extension depending on presenter + * + * @return string + */ + private function generateDownloadFileExtension(): string + { + return match (get_class($this->presenter)) { + CsvPresenter::class => self::CSV_FILE_EXTENSION, + JsonPresenter::class => self::JSON_FILE_EXTENSION, + default => '', + }; + } + + /** + * Generates download file name (name + extension depending on used presenter) + * + * @return string + */ + private function generateDownloadFileName(): string + { + $fileExtension = $this->generateDownloadFileExtension(); + if ($fileExtension === '') { + return $this->downloadFileName; + } + + return $this->downloadFileName . '.' . $fileExtension; + } +} diff --git a/src/Core/Infrastructure/Common/Presenter/JsonPresenter.php b/src/Core/Infrastructure/Common/Presenter/JsonPresenter.php index 807244d1858..e798bc66731 100644 --- a/src/Core/Infrastructure/Common/Presenter/JsonPresenter.php +++ b/src/Core/Infrastructure/Common/Presenter/JsonPresenter.php @@ -24,8 +24,6 @@ namespace Core\Infrastructure\Common\Presenter; use Centreon\Domain\Log\LoggerTrait; -use Core\Application\Common\UseCase\BodyResponseInterface; -use Core\Application\Common\UseCase\ResponseStatusInterface; use Core\Application\Common\UseCase\CreatedResponse; use Core\Application\Common\UseCase\ErrorResponse; use Core\Application\Common\UseCase\InvalidArgumentResponse; @@ -35,41 +33,13 @@ use Core\Application\Common\UseCase\NoContentResponse; use Symfony\Component\HttpFoundation\JsonResponse; use Core\Application\Common\UseCase\NotFoundResponse; -use Core\Infrastructure\Common\Presenter\PresenterFormatterInterface; -class JsonPresenter implements PresenterFormatterInterface +class JsonPresenter extends AbstractPresenter implements PresenterFormatterInterface { use LoggerTrait; - /** - * @var mixed $data - */ private mixed $data = null; - /** - * @var mixed[] $responseHeaders - */ - private array $responseHeaders = []; - - /** - * @inheritDoc - */ - public function setResponseHeaders(array $responseHeaders): void - { - $this->responseHeaders = $responseHeaders; - } - - /** - * @inheritDoc - */ - public function getResponseHeaders(): array - { - return $this->responseHeaders; - } - - /** - * @inheritDoc - */ public function present(mixed $data): void { $this->data = $data; @@ -83,88 +53,28 @@ public function show(): JsonResponse switch (true) { case is_a($this->data, NotFoundResponse::class, false): $this->debug('Data not found. Generating a not found response'); - return new JsonResponse( - $this->formatErrorContent($this->data, JsonResponse::HTTP_NOT_FOUND), - JsonResponse::HTTP_NOT_FOUND, - $this->responseHeaders, - ); + return $this->generateJsonErrorResponse($this->data, JsonResponse::HTTP_NOT_FOUND); case is_a($this->data, ErrorResponse::class, false): $this->debug('Data error. Generating an error response'); - return new JsonResponse( - $this->formatErrorContent($this->data, JsonResponse::HTTP_INTERNAL_SERVER_ERROR), - JsonResponse::HTTP_INTERNAL_SERVER_ERROR, - $this->responseHeaders, - ); + return $this->generateJsonErrorResponse($this->data, JsonResponse::HTTP_INTERNAL_SERVER_ERROR); case is_a($this->data, InvalidArgumentResponse::class, false): $this->debug('Invalid argument. Generating an error response'); - return new JsonResponse( - $this->formatErrorContent($this->data, JsonResponse::HTTP_BAD_REQUEST), - JsonResponse::HTTP_BAD_REQUEST, - $this->responseHeaders, - ); + return $this->generateJsonErrorResponse($this->data, JsonResponse::HTTP_BAD_REQUEST); case is_a($this->data, UnauthorizedResponse::class, false): $this->debug('Unauthorized. Generating an error response'); - return new JsonResponse( - $this->formatErrorContent($this->data, JsonResponse::HTTP_UNAUTHORIZED), - JsonResponse::HTTP_UNAUTHORIZED, - $this->responseHeaders, - ); + return $this->generateJsonErrorResponse($this->data, JsonResponse::HTTP_UNAUTHORIZED); case is_a($this->data, PaymentRequiredResponse::class, false): $this->debug('Payment required. Generating an error response'); - return new JsonResponse( - $this->formatErrorContent($this->data, JsonResponse::HTTP_PAYMENT_REQUIRED), - JsonResponse::HTTP_PAYMENT_REQUIRED, - $this->responseHeaders, - ); + return $this->generateJsonErrorResponse($this->data, JsonResponse::HTTP_PAYMENT_REQUIRED); case is_a($this->data, ForbiddenResponse::class, false): $this->debug('Forbidden. Generating an error response'); - return new JsonResponse( - $this->formatErrorContent($this->data, JsonResponse::HTTP_FORBIDDEN), - JsonResponse::HTTP_FORBIDDEN, - $this->responseHeaders, - ); + return $this->generateJsonErrorResponse($this->data, JsonResponse::HTTP_FORBIDDEN); case is_a($this->data, CreatedResponse::class, false): - return new JsonResponse( - null, - JsonResponse::HTTP_CREATED, - $this->responseHeaders, - ); + return $this->generateJsonResponse(null, JsonResponse::HTTP_CREATED); case is_a($this->data, NoContentResponse::class, false): - return new JsonResponse( - null, - JsonResponse::HTTP_NO_CONTENT, - $this->responseHeaders, - ); + return $this->generateJsonResponse(null, JsonResponse::HTTP_NO_CONTENT); default: - return new JsonResponse( - $this->data, - JsonResponse::HTTP_OK, - $this->responseHeaders, - ); - } - } - - /** - * Format content on error - * - * @param mixed $data - * @param integer $code - * @return mixed[]|null - */ - private function formatErrorContent(mixed $data, int $code): ?array - { - $content = null; - - if (is_a($data, ResponseStatusInterface::class)) { - $content = [ - 'code' => $code, - 'message' => $data->getMessage(), - ]; - if (is_a($data, BodyResponseInterface::class)) { - $content = array_merge($content, $data->getBody()); - } + return $this->generateJsonResponse($this->data, JsonResponse::HTTP_OK); } - - return $content; } } diff --git a/src/Core/Infrastructure/RealTime/Api/DownloadPerformanceMetrics/DownloadPerformanceMetricsController.php b/src/Core/Infrastructure/RealTime/Api/DownloadPerformanceMetrics/DownloadPerformanceMetricsController.php new file mode 100644 index 00000000000..7e51322bb0b --- /dev/null +++ b/src/Core/Infrastructure/RealTime/Api/DownloadPerformanceMetrics/DownloadPerformanceMetricsController.php @@ -0,0 +1,120 @@ +denyAccessUnlessGrantedForApiRealtime(); + + $this->request = $request; + $this->createPerformanceMetricRequest($hostId, $serviceId); + + $useCase($this->performanceMetricRequest, $presenter); + return $presenter->show(); + } + + /** + * Creates a performance metric request depending request parameters + * + * @param int $hostId + * @param int $serviceId + * @return void + */ + private function createPerformanceMetricRequest(int $hostId, int $serviceId): void + { + $this->findStartDate(); + $this->findEndDate(); + + $this->performanceMetricRequest = new FindPerformanceMetricRequest( + $hostId, + $serviceId, + $this->startDate, + $this->endDate + ); + } + + /** + * Populates startDate attribute with start_date parameter value from http request + * + * @throws \Exception + * @return void + */ + private function findStartDate(): void + { + $this->startDate = $this->findDateInRequest(self::START_DATE_PARAMETER_NAME); + } + + /** + * Populates endDate attribute with end_date parameter value from http request + * + * @throws \Exception + * @return void + */ + private function findEndDate(): void + { + $this->endDate = $this->findDateInRequest(self::END_DATE_PARAMETER_NAME); + } + + /** + * Retrieves date attribute from http request parameter identified by $parameterName + * + * @param string $parameterName + * @throws \Exception + * @return DateTimeImmutable + */ + private function findDateInRequest(string $parameterName): DateTimeImmutable + { + $dateParameter = $this->request->query->get($parameterName); + + if (is_null($dateParameter)) { + $errorMessage = 'Unable to find date parameter ' . $parameterName . ' into the http request'; + throw new \InvalidArgumentException($errorMessage); + } + + return new DateTimeImmutable((string) $dateParameter); + } +} diff --git a/src/Core/Infrastructure/RealTime/Api/DownloadPerformanceMetrics/DownloadPerformanceMetricsPresenter.php b/src/Core/Infrastructure/RealTime/Api/DownloadPerformanceMetrics/DownloadPerformanceMetricsPresenter.php new file mode 100644 index 00000000000..0c105ca9750 --- /dev/null +++ b/src/Core/Infrastructure/RealTime/Api/DownloadPerformanceMetrics/DownloadPerformanceMetricsPresenter.php @@ -0,0 +1,53 @@ +performanceMetrics); + } + + /** + * Sets download file name in presenter + * + * @inheritDoc + */ + public function setDownloadFileName(string $fileName): void + { + if ($this->presenterFormatter instanceof DownloadInterface) { + $this->presenterFormatter->setDownloadFileName($fileName); + } + } +} diff --git a/src/Core/Infrastructure/RealTime/Repository/DataBin/DbReadPerformanceDataRepository.php b/src/Core/Infrastructure/RealTime/Repository/DataBin/DbReadPerformanceDataRepository.php new file mode 100644 index 00000000000..517c9be81a5 --- /dev/null +++ b/src/Core/Infrastructure/RealTime/Repository/DataBin/DbReadPerformanceDataRepository.php @@ -0,0 +1,108 @@ +db = $db; + } + + /** + * Retrieves raw data_bin with filters + * + * @param array $metrics + * @return iterable + */ + public function findDataByMetricsAndDates( + array $metrics, + DateTimeInterface $startDate, + DateTimeInterface $endDate + ): iterable { + $this->db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); + $this->db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); + + $columns = ['ctime AS time']; + $pattern = 'AVG(CASE WHEN id_metric = %d THEN `value` end) AS %s'; + foreach ($metrics as $metric) { + $columns[] = sprintf($pattern, $metric->getId(), $metric->getName()); + } + + $query = sprintf( + 'SELECT %s FROM `:dbstg`.data_bin WHERE ctime >= :start AND ctime < :end GROUP BY time', + join(',', $columns) + ); + + $statement = $this->db->prepare($this->translateDbName($query)); + + $statement->bindValue(':start', $startDate->getTimestamp(), PDO::PARAM_INT); + $statement->bindValue(':end', $endDate->getTimestamp(), PDO::PARAM_INT); + $statement->execute(); + + foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $dataBin) { + yield $this->createPerformanceMetricFromDataBin($dataBin); + } + + $statement->closeCursor(); + } + + /** + * @param array $dataBin + */ + private function createPerformanceMetricFromDataBin(array $dataBin): PerformanceMetric + { + $time = (new \DateTimeImmutable())->setTimestamp((int) $dataBin['time']); + $metricValues = $this->createMetricValues($dataBin); + + return new PerformanceMetric($time, $metricValues); + } + + /** + * @param array $data + * @return MetricValue[] + */ + private function createMetricValues(array $data): array + { + $metricValues = []; + foreach ($data as $columnName => $columnValue) { + if ($columnName !== 'time') { + $metricValues[] = new MetricValue($columnName, (float) $columnValue); + } + } + return $metricValues; + } +} diff --git a/src/Core/Infrastructure/RealTime/Repository/FindIndex/DbReadIndexDataRepository.php b/src/Core/Infrastructure/RealTime/Repository/FindIndex/DbReadIndexDataRepository.php new file mode 100644 index 00000000000..9bd0013050e --- /dev/null +++ b/src/Core/Infrastructure/RealTime/Repository/FindIndex/DbReadIndexDataRepository.php @@ -0,0 +1,81 @@ +db = $db; + } + + /** + * @inheritDoc + */ + public function findIndexByHostIdAndServiceId(int $hostId, int $serviceId): int + { + $query = 'SELECT id FROM `:dbstg`.index_data WHERE host_id = :hostId AND service_id = :serviceId'; + $statement = $this->db->prepare($this->translateDbName($query)); + $statement->bindValue(':hostId', $hostId, PDO::PARAM_INT); + $statement->bindValue(':serviceId', $serviceId, PDO::PARAM_INT); + $statement->execute(); + + $row = $statement->fetch(); + + if (!is_array($row) || !array_key_exists('id', $row)) { + throw new \InvalidArgumentException('Resource not found'); + } + + return (int) $row['id']; + } + + /** + * @inheritDoc + */ + public function findHostNameAndServiceDescriptionByIndex(int $index): ?IndexData + { + $query = 'SELECT host_name as hostName, service_description as serviceDescription '; + $query .= ' FROM `:dbstg`.index_data WHERE id = :index'; + $statement = $this->db->prepare($this->translateDbName($query)); + $statement->bindValue(':index', $index, PDO::PARAM_INT); + $statement->execute(); + + $record = $statement->fetch(); + + if (!is_array($record)) { + return null; + } + + return new IndexData($record['hostName'], $record['serviceDescription']); + } +} diff --git a/src/Core/Infrastructure/RealTime/Repository/FindMetric/DbReadMetricRepository.php b/src/Core/Infrastructure/RealTime/Repository/FindMetric/DbReadMetricRepository.php new file mode 100644 index 00000000000..9c0573fc6ec --- /dev/null +++ b/src/Core/Infrastructure/RealTime/Repository/FindMetric/DbReadMetricRepository.php @@ -0,0 +1,65 @@ +db = $db; + } + + /** + * @return array + */ + public function findMetricsByIndexId(int $indexId): array + { + $query = 'SELECT DISTINCT metric_id as id, metric_name as name FROM `:dbstg`.metrics, `:dbstg`.index_data '; + $query .= ' WHERE metrics.index_id = index_data.id AND id = :index_id ORDER BY metric_id'; + $statement = $this->db->prepare($this->translateDbName($query)); + $statement->bindValue(':index_id', $indexId, \PDO::PARAM_INT); + $statement->execute(); + + $records = $statement->fetchAll(); + if (!is_array($records) || count($records) === 0) { + return []; + } + + $metrics = []; + foreach ($records as $record) { + $metrics[] = new Metric((int) $record['id'], $record['name']); + } + + return $metrics; + } +} diff --git a/tests/php/Core/Application/Platform/UseCase/FindInstallationStatus/FindInstallationStatusTest.php b/tests/php/Core/Application/Platform/UseCase/FindInstallationStatus/FindInstallationStatusTest.php index 8d31862f599..8fac915f4a8 100644 --- a/tests/php/Core/Application/Platform/UseCase/FindInstallationStatus/FindInstallationStatusTest.php +++ b/tests/php/Core/Application/Platform/UseCase/FindInstallationStatus/FindInstallationStatusTest.php @@ -26,7 +26,6 @@ use PHPUnit\Framework\TestCase; use Core\Application\Platform\Repository\ReadPlatformRepositoryInterface; use Core\Application\Platform\UseCase\FindInstallationStatus\FindInstallationStatus; -use Tests\Core\Application\Platform\UseCase\FindInstallationStatus\FindInstallationStatusPresenterStub; class FindInstallationStatusTest extends TestCase { diff --git a/tests/php/Core/Application/RealTime/UseCase/FindPerformanceMetrics/FindPerformanceMetricResponseTest.php b/tests/php/Core/Application/RealTime/UseCase/FindPerformanceMetrics/FindPerformanceMetricResponseTest.php new file mode 100644 index 00000000000..dc99244b8b1 --- /dev/null +++ b/tests/php/Core/Application/RealTime/UseCase/FindPerformanceMetrics/FindPerformanceMetricResponseTest.php @@ -0,0 +1,92 @@ + $rta, 'pl' => $pl, 'rtmax' => $rtmax, 'rtmin' => $rtmin]; + foreach ($metrics as $columnName => $columnValue) { + $metricValues[] = new MetricValue($columnName, $columnValue); + } + + return new PerformanceMetric(new DateTimeImmutable($date), $metricValues); +} + +/** + * @return array + */ +function generateExpectedResponseData(string $date, float $rta, float $pl, float $rtmax, float $rtmin): array +{ + $dateTime = new DateTimeImmutable($date); + + return [ + 'time' => $dateTime->getTimestamp(), + 'humantime' => $dateTime->format('Y-m-d H:i:s'), + 'rta' => sprintf('%f', $rta), + 'pl' => sprintf('%f', $pl), + 'rtmax' => sprintf('%f', $rtmax), + 'rtmin' => sprintf('%f', $rtmin), + ]; +} + +it( + 'response contains properly formatted performanceMetrics', + function (iterable $performanceMetrics, array $expectedResponseData) { + $response = new FindPerformanceMetricResponse($performanceMetrics); + + $this->assertTrue(property_exists($response, 'performanceMetrics')); + $this->assertInstanceOf(\Generator::class, $response->performanceMetrics); + + $actualResponseData = array(...$response->performanceMetrics); + $this->assertSame($expectedResponseData, $actualResponseData); + } +)->with([ + [ + [], [] + ], + [ + [ + createPerformanceMetric('2022-01-01', 0.039, 0, 0.108, 0.0049) + ], + [ + generateExpectedResponseData('2022-01-01', 0.039, 0, 0.108, 0.0049) + ] + ], + [ + [ + createPerformanceMetric('2022-01-01', 0.039, 0, 0.108, 0.0049), + createPerformanceMetric('2022-01-01 11:00:05', 0.04, 0.1, 0.10, 0.006) + ], + [ + generateExpectedResponseData('2022-01-01', 0.039, 0, 0.108, 0.0049), + generateExpectedResponseData('2022-01-01 11:00:05', 0.04, 0.1, 0.10, 0.006) + ] + ] +]); diff --git a/tests/php/Core/Application/RealTime/UseCase/FindPerformanceMetrics/FindPerformanceMetricsTest.php b/tests/php/Core/Application/RealTime/UseCase/FindPerformanceMetrics/FindPerformanceMetricsTest.php new file mode 100644 index 00000000000..6933a824a1a --- /dev/null +++ b/tests/php/Core/Application/RealTime/UseCase/FindPerformanceMetrics/FindPerformanceMetricsTest.php @@ -0,0 +1,158 @@ +hostId = 1; + $this->serviceId = 2; + $this->indexId = 15; +}); + +it( + 'download file name is properly generated', + function (string $hostName, string $serviceDescription, string $expectedFileName) { + $indexData = new IndexData($hostName, $serviceDescription); + + $indexDataRepository = $this->createMock(ReadIndexDataRepositoryInterface::class); + $indexDataRepository + ->expects($this->once()) + ->method('findIndexByHostIdAndServiceId') + ->with( + $this->equalTo($this->hostId), + $this->equalTo($this->serviceId), + ) + ->willReturn($this->indexId); + + $indexDataRepository + ->expects($this->once()) + ->method('findHostNameAndServiceDescriptionByIndex') + ->willReturn($indexData); + + $metricRepository = $this->createMock(ReadMetricRepositoryInterface::class); + $performanceDataRepository = $this->createMock(ReadPerformanceDataRepositoryInterface::class); + $presenter = $this->createMock(FindPerformanceMetricPresenterInterface::class); + $presenter + ->expects($this->once()) + ->method('setDownloadFileName') + ->with($this->equalTo($expectedFileName)); + + $performanceMetricRequest = new FindPerformanceMetricRequest( + $this->hostId, + $this->serviceId, + new DateTimeImmutable('2022-01-01'), + new DateTimeImmutable('2023-01-01') + ); + + $sut = new FindPerformanceMetrics($indexDataRepository, $metricRepository, $performanceDataRepository); + + $sut($performanceMetricRequest, $presenter); + } +)->with([ + ['Centreon-Server', 'Ping', 'Centreon-Server_Ping'], + ['', 'Ping', '15'], + ['Centreon-Server', '', '15'], + ['', '', '15'], +]); + +it( + 'validate presenter response', + function (iterable $performanceData, FindPerformanceMetricResponse $expectedResponse) { + $indexDataRepository = $this->createMock(ReadIndexDataRepositoryInterface::class); + $indexDataRepository + ->expects($this->once()) + ->method('findIndexByHostIdAndServiceId') + ->with( + $this->equalTo($this->hostId), + $this->equalTo($this->serviceId), + ) + ->willReturn($this->indexId); + $indexDataRepository + ->expects($this->once()) + ->method('findHostNameAndServiceDescriptionByIndex') + ->willReturn(null); + + $metricRepository = $this->createMock(ReadMetricRepositoryInterface::class); + $performanceDataRepository = $this->createMock(ReadPerformanceDataRepositoryInterface::class); + $performanceDataRepository + ->expects($this->once()) + ->method('findDataByMetricsAndDates') + ->willReturn($performanceData); + + $presenter = $this->createMock(FindPerformanceMetricPresenterInterface::class); + $presenter + ->expects($this->once()) + ->method('present') + ->with($this->equalTo($expectedResponse)); + + + $performanceMetricRequest = new FindPerformanceMetricRequest( + $this->hostId, + $this->serviceId, + new DateTimeImmutable('2022-02-01'), + new DateTimeImmutable('2023-01-01') + ); + + $sut = new FindPerformanceMetrics($indexDataRepository, $metricRepository, $performanceDataRepository); + + $sut($performanceMetricRequest, $presenter); + } +)->with([ + [ + [['rta' => 0.01]], + new FindPerformanceMetricResponse( + [ + new PerformanceMetric( + new DateTimeImmutable(), + [new MetricValue('rta', 0.001)] + ) + ] + ) + ], + [ + [['rta' => 0.01], ['pl' => 0.02]], + new FindPerformanceMetricResponse( + [ + new PerformanceMetric( + new DateTimeImmutable(), + [ + new MetricValue('rta', 0.001), + new MetricValue('pl', 0.002), + ] + ), + ] + ) + ] +]);