Skip to content

Commit

Permalink
🎉 first working version
Browse files Browse the repository at this point in the history
  • Loading branch information
Kanti committed May 13, 2021
0 parents commit 5221855
Show file tree
Hide file tree
Showing 24 changed files with 1,566 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
vendor
public
node_modules
52 changes: 52 additions & 0 deletions Classes/Command/GenerateRandomTestDataCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Kanti\WebVitalsTracker\Command;

use Kanti\WebVitalsTracker\Domain\Repository\MeasureRepository;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use TYPO3\CMS\Core\Database\ConnectionPool;

final class GenerateRandomTestDataCommand extends Command
{
public function __construct(string $name = null, private ConnectionPool $connectionPool)
{
parent::__construct($name);
}

public function execute(InputInterface $input, OutputInterface $output): int
{
$count = 1_000_000;
$pageUid = 1;
$sysLanguageUid = 0;
$daysBack = 90;

$progressBar = new ProgressBar($output, $count);
for ($i = 0; $i < $count; $i++) {
$uuid = md5(random_bytes(50));

$queryBuilder = $this->connectionPool->getQueryBuilderForTable(MeasureRepository::TABLENAME);
$queryBuilder->insert(MeasureRepository::TABLENAME)
->values(
[
'uuid' => $uuid,
'page_id' => $pageUid,
'sys_language' => $sysLanguageUid,
'cls' => random_int(0, 1 * 100) / 100,
'fcp' => random_int(0, 10_000 * 100) / 100,
'fid' => random_int(0, 10_000 * 100) / 100,
'lcp' => random_int(0, 10_000 * 100) / 100,
'ttfb' => random_int(0, 10_000 * 100) / 100,
'date' => date("Y-m-d H:i:s", random_int(time() - ($daysBack * 24 * 60 * 60), time())),
]
)->execute();
$progressBar->advance();
}
$progressBar->finish();
return 0;
}
}
82 changes: 82 additions & 0 deletions Classes/Domain/Repository/MeasureRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

declare(strict_types=1);

namespace Kanti\WebVitalsTracker\Domain\Repository;

use Doctrine\DBAL\ForwardCompatibility\DriverStatement;
use TYPO3\CMS\Core\Database\ConnectionPool;

final class MeasureRepository
{
public const TABLENAME = 'tx_webvitalstracker_measure';

public function __construct(private ConnectionPool $connectionPool)
{
}

/**
* @param int $pageId
* @param int|null $sysLanguageUid
* @return array<int, array<string, mixed>>|null
* @throws \Doctrine\DBAL\Exception
*/
public function findData(int $pageId, int $sysLanguageUid = null): ?array
{
$queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLENAME)->getConcreteQueryBuilder();
$queryBuilder->select(
'COUNT(*) as requestCount',
'AVG(cls)*100 as cls',
'AVG(fcp) as fcp',
'AVG(fid) as fid',
'AVG(lcp) as lcp',
'AVG(ttfb) as ttfb',
)->from(self::TABLENAME)
->where($queryBuilder->expr()->eq('page_id', $queryBuilder->createNamedParameter($pageId)))
->groupBy('page_id');

if ($sysLanguageUid !== null) {
$queryBuilder->andWhere($queryBuilder->expr()->eq('sys_language', $queryBuilder->createNamedParameter($sysLanguageUid)));
}
$statement = $queryBuilder->execute();
assert($statement instanceof DriverStatement);
return $statement->fetch() ?: null;
}

public function insertOrUpdateMeasure(string $uuid, string $name, float $value, int $counter, int $pageUid, int $sysLanguageUid): void
{
$possibleNames = ['cls', 'fcp', 'fid', 'lcp', 'ttfb'];
if (!in_array($name, $possibleNames, true)) {
throw new \InvalidArgumentException(sprintf('measure name must be one of %s got %s', implode(',', $possibleNames), $name));
}
$queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLENAME);
$sql = $queryBuilder->insert(self::TABLENAME)
->values(
[
'uuid' => $uuid,
'counter_' . $name => $counter,
'page_id' => $pageUid,
'sys_language' => $sysLanguageUid,
$name => $value,
]
)
->getSQL();

$sql = preg_replace('/(INSERT INTO)/', 'INSERT IGNORE INTO', $sql);
assert(is_string($sql));
$inserted = (bool)$this->connectionPool->getConnectionForTable(self::TABLENAME)
->executeStatement($sql, $queryBuilder->getParameters());

if (!$inserted) {
$queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLENAME);
$queryBuilder->update(self::TABLENAME)
->set($name, $queryBuilder->createNamedParameter($value), false)
->set('counter_' . $name, $queryBuilder->createNamedParameter($counter), false)
->where($queryBuilder->expr()->eq('uuid', $queryBuilder->createNamedParameter($uuid)))
->andWhere($queryBuilder->expr()->eq('page_id', $queryBuilder->createNamedParameter($pageUid)))
->andWhere($queryBuilder->expr()->eq('sys_language', $queryBuilder->createNamedParameter($sysLanguageUid)))
->andWhere($queryBuilder->expr()->lt('counter_' . $name, $queryBuilder->createNamedParameter($counter)))
->execute();
}
}
}
47 changes: 47 additions & 0 deletions Classes/Hooks/PageHeaderHook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace Kanti\WebVitalsTracker\Hooks;

use Kanti\WebVitalsTracker\Domain\Repository\MeasureRepository;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Fluid\View\StandaloneView;

final class PageHeaderHook
{
public function __construct(
private MeasureRepository $measureRepository,
private StandaloneView $templateView,
private PageRenderer $pageRenderer
) {

$this->templateView->getRenderingContext()->getTemplatePaths()->fillDefaultsByPackageName('web_vitals_tracker');
$this->templateView->setTemplate('PageHeaderHook');
}

public function render(): string
{
$pageId = (int)$_GET['id'];
// $sysLanguageUid = (int)BackendUtility::getModuleData(['language'], [], 'web_layout')['language'];
$hasAccess = $GLOBALS['BE_USER']->check('non_exclude_fields', 'pages:tx_webvitalstracker_measure');
// $disableWarnings = (bool)($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['web_vitals_tracker']['disableWarnings'] ?? false);
$disableInfo = (bool)($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['web_vitals_tracker']['disableInfo'] ?? false);

if (!$hasAccess || $disableInfo) {
return '';
}

$data = $this->measureRepository->findData($pageId, /*$sysLanguageUid*/ null);
if (empty($data)) {
return '';
}

$this->pageRenderer->addCssFile('EXT:web_vitals_tracker/Resources/Public/Css/DrawHeaderHook.css');

$this->templateView->assignMultiple($data);

return $this->templateView->render();
}
}
57 changes: 57 additions & 0 deletions Classes/Middleware/MeasureMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Kanti\WebVitalsTracker\Middleware;

use Kanti\WebVitalsTracker\Domain\Repository\MeasureRepository;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Http\NullResponse;
use TYPO3\CMS\Core\Routing\PageArguments;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Utility\GeneralUtility;

final class MeasureMiddleware implements MiddlewareInterface
{

public function __construct(private ContainerInterface $container)
{
}

/**
* @throws \JsonException
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if (!isset($request->getQueryParams()['webvitalstracker'])) {
return $handler->handle($request);
}

$pageArguments = $request->getAttribute('routing');
assert($pageArguments instanceof PageArguments);

$language = $request->getAttribute('language');
assert($language instanceof SiteLanguage);

$measureRepository = $this->container->get(MeasureRepository::class);
assert($measureRepository instanceof MeasureRepository);

$bodyString = $request->getBody()->getContents();
$body = \json_decode($bodyString, true, 512, JSON_THROW_ON_ERROR);

$measureRepository->insertOrUpdateMeasure(
$body['requestUuid'],
strtolower($body['name']),
(float)$body['value'],
(int)$body['counter'],
$pageArguments->getPageId(),
$language->getLanguageId()
);

return new NullResponse();
}
}
12 changes: 12 additions & 0 deletions Configuration/RequestMiddlewares.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

return [
'frontend' => [
'Kanti/web_vitals_tracker/measure' => [
'target' => \Kanti\WebVitalsTracker\Middleware\MeasureMiddleware::class,
'after' => [
'typo3/cms-frontend/page-resolver'
]
],
],
];
21 changes: 21 additions & 0 deletions Configuration/Services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false

Kanti\WebVitalsTracker\:
resource: '../Classes/*'

Kanti\WebVitalsTracker\Middleware\MeasureMiddleware:
public: true

Kanti\WebVitalsTracker\Domain\Repository\MeasureRepository:
public: true

Kanti\WebVitalsTracker\Hooks\PageHeaderHook:
public: true

Kanti\WebVitalsTracker\Command\GenerateRandomTestDataCommand:
tags:
- { name: 'console.command', command: 'webvitalstracker:generateTestData', description: 'Generates random test data vor the web_vitals_tracker extension' }
Empty file.
2 changes: 2 additions & 0 deletions Configuration/TypoScript/setup.typoscript
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
page.headerData.932132 = TEXT
page.headerData.932132.value = <script src="/typo3conf/ext/web_vitals_tracker/Resources/Public/JavaScript/performance.min.js" type="module"></script>
Binary file added Documentation/Images/wev_vitals_screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 5221855

Please sign in to comment.