Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Add timeframe filter to statistics module #4231

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 28 additions & 10 deletions Classes/Controller/Backend/Search/InfoModuleController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

use ApacheSolrForTypo3\Solr\Api;
use ApacheSolrForTypo3\Solr\Domain\Search\ApacheSolrDocument\Repository as ApacheSolrDocumentRepository;
use ApacheSolrForTypo3\Solr\Domain\Search\Statistics\StatisticsFilterDto;
use ApacheSolrForTypo3\Solr\Domain\Search\Statistics\StatisticsRepository;
use ApacheSolrForTypo3\Solr\System\Solr\ResponseAdapter;
use ApacheSolrForTypo3\Solr\System\Validator\Path;
Expand Down Expand Up @@ -49,15 +50,21 @@ protected function initializeAction()
*
* @return ResponseInterface
*/
public function indexAction(): ResponseInterface
{
public function indexAction(
?StatisticsFilterDto $statisticsFilter = null,
int $activeTabId = 0,
string $operation = ''
): ResponseInterface {
$this->initializeAction();

$this->view->assign('activeTabId', $activeTabId);
if ($this->selectedSite === null) {
$this->view->assign('can_not_proceed', true);
return $this->getModuleTemplateResponse();
}

$this->collectConnectionInfos();
$this->collectStatistics();
$this->collectStatistics($statisticsFilter, $operation);
$this->collectIndexFieldsInfo();
$this->collectIndexInspectorInfo();

Expand Down Expand Up @@ -129,37 +136,39 @@ protected function collectConnectionInfos(): void
/**
* Index action, shows an overview of the state of the Solr index
*/
protected function collectStatistics(): void
protected function collectStatistics(?StatisticsFilterDto $statisticsFilterDto, string $operation): void
{
// TODO make time frame user adjustable, for now it's last 30 days
$statisticsFilter = $this->getStatisticsFilter($statisticsFilterDto, $operation);

$siteRootPageId = $this->selectedSite->getRootPageId();
/* @var StatisticsRepository $statisticsRepository */
/** @var StatisticsRepository $statisticsRepository */
$statisticsRepository = GeneralUtility::makeInstance(StatisticsRepository::class);

// @TODO: Do we want Typoscript constants to restrict the results?
$this->view->assign(
'top_search_phrases',
$statisticsRepository->getTopKeyWordsWithHits($siteRootPageId, 30, 5)
$statisticsRepository->getTopKeyWordsWithHits($statisticsFilter)
);
$this->view->assign(
'top_search_phrases_without_hits',
$statisticsRepository->getTopKeyWordsWithoutHits($siteRootPageId, 30, 5)
$statisticsRepository->getTopKeyWordsWithoutHits($statisticsFilter)
);
$this->view->assign(
'search_phrases_statistics',
$statisticsRepository->getSearchStatistics($siteRootPageId, 30, 100)
$statisticsRepository->getSearchStatistics($statisticsFilter)
);

$labels = [];
$data = [];
$chartData = $statisticsRepository->getQueriesOverTime($siteRootPageId, 30, 86400);
$chartData = $statisticsRepository->getQueriesOverTime($statisticsFilter, 86400);

foreach ($chartData as $bucket) {
// @todo Replace deprecated strftime in php 8.1. Suppress warning for now
$labels[] = @strftime('%x', $bucket['timestamp']);
$data[] = (int)$bucket['numQueries'];
}

$this->view->assign('statisticsFilter', $statisticsFilter);
$this->view->assign('queriesChartLabels', json_encode($labels));
$this->view->assign('queriesChartData', json_encode($data));
}
Expand Down Expand Up @@ -294,4 +303,13 @@ protected function getCoreMetrics(ResponseAdapter $lukeData, array $fields): arr
'numberOfFields' => count($fields),
];
}

protected function getStatisticsFilter(?StatisticsFilterDto $statisticsFilterDto, string $operation): StatisticsFilterDto
{
if ($statisticsFilterDto === null || $operation === 'reset-filters') {
$statisticsFilterDto = GeneralUtility::makeInstance(StatisticsFilterDto::class);
}

return $statisticsFilterDto->setSiteRootPageId($this->selectedSite->getRootPageId());
}
}
137 changes: 137 additions & 0 deletions Classes/Domain/Search/Statistics/StatisticsFilterDto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php

declare(strict_types=1);

namespace ApacheSolrForTypo3\Solr\Domain\Search\Statistics;

use DateTime;

final class StatisticsFilterDto
{
/** typoscript constants */
private int $siteRootPageId = 0;
private int $topHitsLimit = 5;
private int $noHitsLimit = 5;
private int $queriesLimit = 100;
private int $topHitsDays = 30;
private int $noHitsDays = 30;
private int $queriesDays = 30;

/** Override properties */
private ?DateTime $startDate = null;
private ?DateTime $endDate = null;

public function __construct()
{
$this->startDate = DateTime::createFromFormat('U', (string)$this->getQueriesStartDate());
$this->endDate = DateTime::createFromFormat('U', (string)$this->getEndDateTimestamp());
}

public function setSiteRootPageId(int $siteRootPageId): StatisticsFilterDto
{
$this->siteRootPageId = $siteRootPageId;
return $this;
}

public function setStartDate(?DateTime $startDate): StatisticsFilterDto
{
$this->startDate = $startDate;
return $this;
}

public function setEndDate(?DateTime $endDate): StatisticsFilterDto
{
$this->endDate = $endDate;
return $this;
}

public function getTopHitsDays(): int
{
return $this->topHitsDays;
}

public function getNoHitsDays(): int
{
return $this->noHitsDays;
}

public function getQueriesDays(): int
{
return $this->queriesDays;
}

public function getSiteRootPageId(): int
{
return $this->siteRootPageId;
}

public function getTopHitsLimit(): int
{
return $this->topHitsLimit;
}

public function getNoHitsLimit(): int
{
return $this->noHitsLimit;
}

public function getQueriesLimit(): int
{
return $this->queriesLimit;
}

public function getStartDate(): ?DateTime
{
return $this->startDate;
}

public function getEndDate(): ?DateTime
{
return $this->endDate;
}

public function getTopHitsStartDate(): int
{
if ($this->startDate !== null) {
return $this->startDate->getTimestamp();
}

return $this->getTimeStampSinceDays($this->topHitsDays);
}

public function getNoHitsStartDate(): int
{
if ($this->startDate !== null) {
return $this->startDate->getTimestamp();
}

return $this->getTimeStampSinceDays($this->noHitsDays);
}

public function getQueriesStartDate(): int
{
if ($this->startDate !== null) {
return $this->startDate->getTimestamp();
}

return $this->getTimeStampSinceDays($this->queriesDays);
}

/**
* End date can not be set by default in typoscript constants and is always now, so one override getter is enough
*/
public function getEndDateTimestamp(): int
{
if ($this->endDate !== null) {
return $this->endDate->getTimestamp();
}

return $this->getTimeStampSinceDays(0);
}

protected function getTimeStampSinceDays(int $days): int
{
$now = time();
return $now - 86400 * $days; // 86400 seconds/day
}
}
71 changes: 49 additions & 22 deletions Classes/Domain/Search/Statistics/StatisticsRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,16 @@ class StatisticsRepository extends AbstractRepository
* @throws DBALDriverException
* @throws DBALException|\Doctrine\DBAL\DBALException
*/
public function getSearchStatistics(int $rootPageId, int $days = 30, int $limit = 10)
public function getSearchStatistics(StatisticsFilterDto $statisticsFilterDto)
{
$now = time();
$timeStart = (int)($now - 86400 * $days); // 86400 seconds/day
return $this->getPreparedQueryBuilderForSearchStatisticsAndTopKeywords($rootPageId, $timeStart, $limit)
->execute()->fetchAllAssociative();
return $this->getPreparedQueryBuilderForSearchStatisticsAndTopKeywords(
$statisticsFilterDto->getSiteRootPageId(),
$statisticsFilterDto->getQueriesStartDate(),
$statisticsFilterDto->getEndDateTimestamp(),
$statisticsFilterDto->getQueriesLimit()
)
->execute()
->fetchAllAssociative();
}

/**
Expand All @@ -63,8 +67,12 @@ public function getSearchStatistics(int $rootPageId, int $days = 30, int $limit
* @throws DBALDriverException
* @throws DBALException|\Doctrine\DBAL\DBALException
*/
protected function getPreparedQueryBuilderForSearchStatisticsAndTopKeywords(int $rootPageId, int $timeStart, int $limit): QueryBuilder
{
protected function getPreparedQueryBuilderForSearchStatisticsAndTopKeywords(
int $rootPageId,
int $timeStart,
int $timeEnd,
int $limit
): QueryBuilder {
$countRows = $this->countByRootPageId($rootPageId);
$queryBuilder = $this->getQueryBuilder();
return $queryBuilder
Expand All @@ -75,6 +83,7 @@ protected function getPreparedQueryBuilderForSearchStatisticsAndTopKeywords(int
->from($this->table)
->andWhere(
$queryBuilder->expr()->gt('tstamp', $timeStart),
$queryBuilder->expr()->lt('tstamp', $timeEnd),
$queryBuilder->expr()->eq('root_pid', $rootPageId)
)
->groupBy('keywords')
Expand All @@ -94,9 +103,14 @@ protected function getPreparedQueryBuilderForSearchStatisticsAndTopKeywords(int
* @throws DBALDriverException
* @throws DBALException|\Doctrine\DBAL\DBALException
*/
public function getTopKeyWordsWithHits(int $rootPageId, int $days = 30, int $limit = 10): array
public function getTopKeyWordsWithHits(StatisticsFilterDto $filterDto): array
{
return $this->getTopKeyWordsWithOrWithoutHits($rootPageId, $days, $limit);
return $this->getTopKeyWordsWithOrWithoutHits(
$filterDto->getSiteRootPageId(),
$filterDto->getTopHitsStartDate(),
$filterDto->getEndDateTimestamp(),
$filterDto->getTopHitsLimit()
);
}

/**
Expand All @@ -109,9 +123,15 @@ public function getTopKeyWordsWithHits(int $rootPageId, int $days = 30, int $lim
* @throws DBALDriverException
* @throws DBALException|\Doctrine\DBAL\DBALException
*/
public function getTopKeyWordsWithoutHits(int $rootPageId, int $days = 30, int $limit = 10): array
public function getTopKeyWordsWithoutHits(StatisticsFilterDto $filterDto): array
{
return $this->getTopKeyWordsWithOrWithoutHits($rootPageId, $days, $limit, true);
return $this->getTopKeyWordsWithOrWithoutHits(
$filterDto->getSiteRootPageId(),
$filterDto->getNoHitsStartDate(),
$filterDto->getEndDateTimestamp(),
$filterDto->getNoHitsLimit(),
true
);
}

/**
Expand All @@ -125,12 +145,20 @@ public function getTopKeyWordsWithoutHits(int $rootPageId, int $days = 30, int $
* @throws DBALException|\Doctrine\DBAL\DBALException
* @throws DBALDriverException
*/
protected function getTopKeyWordsWithOrWithoutHits(int $rootPageId, int $days = 30, int $limit = 10, bool $withoutHits = false): array
{
$now = time();
$timeStart = $now - 86400 * $days; // 86400 seconds/day
protected function getTopKeyWordsWithOrWithoutHits(
int $rootPageId,
int $timeStart,
int $timeEnd,
int $limit = 10,
bool $withoutHits = false
): array {
$queryBuilder = $this->getPreparedQueryBuilderForSearchStatisticsAndTopKeywords(
$rootPageId,
$timeStart,
$timeEnd,
$limit
);

$queryBuilder = $this->getPreparedQueryBuilderForSearchStatisticsAndTopKeywords($rootPageId, $timeStart, $limit);
// Check if we want without or with hits
if ($withoutHits === true) {
$queryBuilder->andWhere($queryBuilder->expr()->eq('num_found', 0));
Expand All @@ -151,12 +179,10 @@ protected function getTopKeyWordsWithOrWithoutHits(int $rootPageId, int $days =
* @throws DBALException|\Doctrine\DBAL\DBALException
* @throws DBALDriverException
*/
public function getQueriesOverTime(int $rootPageId, int $days = 30, int $bucketSeconds = 3600): array
public function getQueriesOverTime(StatisticsFilterDto $statisticsFilterDto, int $bucketSeconds = 3600): array
{
$now = time();
$timeStart = $now - 86400 * $days; // 86400 seconds/day

$queryBuilder = $this->getQueryBuilder();

return $queryBuilder
->addSelectLiteral(
'FLOOR(tstamp/' . $bucketSeconds . ') AS bucket',
Expand All @@ -165,8 +191,9 @@ public function getQueriesOverTime(int $rootPageId, int $days = 30, int $bucketS
)
->from($this->table)
->andWhere(
$queryBuilder->expr()->gt('tstamp', $timeStart),
$queryBuilder->expr()->eq('root_pid', $rootPageId)
$queryBuilder->expr()->gt('tstamp', $statisticsFilterDto->getQueriesStartDate()),
$queryBuilder->expr()->lt('tstamp', $statisticsFilterDto->getEndDateTimestamp()),
$queryBuilder->expr()->eq('root_pid', $statisticsFilterDto->getSiteRootPageId())
)
->groupBy('bucket', 'timestamp')
->orderBy('bucket', 'ASC')
Expand Down
12 changes: 12 additions & 0 deletions Resources/Private/Language/locallang.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,18 @@
<trans-unit id="solr.backend.search_statistics_module.search_phrases_header" xml:space="preserve">
<source>Search Phrase Statistics</source>
</trans-unit>
<trans-unit id="solr.backend.search_statistics_module.filter" xml:space="preserve">
<source>Filter</source>
</trans-unit>
<trans-unit id="solr.backend.search_statistics_module.filter.reset" xml:space="preserve">
<source>Reset</source>
</trans-unit>
<trans-unit id="solr.backend.search_statistics_module.filter.start" xml:space="preserve">
<source>Start</source>
</trans-unit>
<trans-unit id="solr.backend.search_statistics_module.filter.end" xml:space="preserve">
<source>End</source>
</trans-unit>

</body>
</file>
Expand Down
Loading
Loading