Skip to content

Commit

Permalink
Dashboard - real data III
Browse files Browse the repository at this point in the history
- added database migration
- added column `Category::$necessary`
- added real data for statistics "Positive consents" and "Positive unique consents"
- added domain event `CategoryNecessaryChanged`
- added query `ScrollThroughConsentsPerPeriodQuery`
  • Loading branch information
tg666 committed Jul 14, 2022
1 parent 5ea5126 commit 7973a3a
Show file tree
Hide file tree
Showing 18 changed files with 507 additions and 34 deletions.
3 changes: 3 additions & 0 deletions config/model/consent/infrastructure.neon
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ services:
-
autowired: no
factory: App\Infrastructure\Consent\Doctrine\ReadModel\GetConsentByProjectIdAndUserIdentifierQueryHandler
-
autowired: no
factory: App\Infrastructure\Consent\Doctrine\ReadModel\ScrollThroughConsentsPerPeriodQueryHandler

# infra: doctrine mapping
nettrine.orm.xml:
Expand Down
35 changes: 13 additions & 22 deletions src/Api/Internal/Controller/StatisticsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ public function getProjectStatistics(ApiRequest $request, ApiResponse $response)
}
}

$projectIds = array_filter($projectIds);

// are some projects inaccessible for the current user?
if (0 < count($inaccessible)) {
return $response->withStatus(ApiResponse::S401_UNAUTHORIZED)
Expand All @@ -111,24 +113,6 @@ public function getProjectStatistics(ApiRequest $request, ApiResponse $response)
]);
}

$missingProjects = array_keys(array_filter($projectIds, static fn (?string $projectId): bool => NULL === $projectId));

// are some projects missing?
if (0 < count($missingProjects)) {
return $response->withStatus(ApiResponse::S422_UNPROCESSABLE_ENTITY)
->writeJsonBody([
'status' => 'error',
'data' => [
'code' => ApiResponse::S422_UNPROCESSABLE_ENTITY,
'error' => sprintf(
'Project%s %s not found.',
1 < count($missingProjects) ? 's' : '',
implode(', ', $missingProjects)
),
],
]);
}

try {
[$startDate, $endDate] = $this->createRange($requestEntity, $userData->timezone);
} catch (Exception $e) {
Expand Down Expand Up @@ -161,13 +145,20 @@ public function getProjectStatistics(ApiRequest $request, ApiResponse $response)
private function buildData(array $projectIdsByCodes, string $locale, DateTimeImmutable $startDate, DateTimeImmutable $endDate, DateTimeZone $userTz): array
{
$data = [];

if (0 >= count($projectIdsByCodes)) {
return $data;
}

$projectIds = array_values($projectIdsByCodes);
$allConsentPeriodStatistics = $this->projectStatisticsCalculator->calculateConsentPeriodStatistics($projectIds, $startDate, $endDate);
$allPositiveConsentPeriodStatistics = $this->projectStatisticsCalculator->calculatePositiveConsentPeriodStatistics($projectIds, $startDate, $endDate);
$allCookieStatistics = $this->projectStatisticsCalculator->calculateCookieStatistics($projectIds);
$allLastConsentDates = $this->projectStatisticsCalculator->calculateLastConsentDate($projectIds);

foreach ($projectIdsByCodes as $code => $projectId) {
$consentPeriodStatistics = $allConsentPeriodStatistics->get($projectId);
$positiveConsentPeriodStatistics = $allPositiveConsentPeriodStatistics->get($projectId);
$cookieStatistics = $allCookieStatistics->get($projectId);
$lastConsentDate = $allLastConsentDates->get($projectId);

Expand All @@ -185,12 +176,12 @@ private function buildData(array $projectIdsByCodes, string $locale, DateTimeImm
'percentageDiff' => $consentPeriodStatistics->uniqueConsentsPeriodStatistics()->percentageDiff(),
],
'allPositive' => [
'value' => 'NaN',
'percentageDiff' => 0,
'value' => $positiveConsentPeriodStatistics->totalConsentsPeriodStatistics()->currentValue(),
'percentageDiff' => $positiveConsentPeriodStatistics->totalConsentsPeriodStatistics()->percentageDiff(),
],
'uniquePositive' => [
'value' => 'NaN',
'percentageDiff' => 0,
'value' => $positiveConsentPeriodStatistics->uniqueConsentsPeriodStatistics()->currentValue(),
'percentageDiff' => $positiveConsentPeriodStatistics->uniqueConsentsPeriodStatistics()->percentageDiff(),
],
'lastConsent' => [
'value' => NULL !== $lastConsentDate ? $lastConsentDate->format(DateTimeInterface::ATOM) : NULL,
Expand Down
5 changes: 5 additions & 0 deletions src/Application/Fixture/resources/demo.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
'en' => 'Functionality cookies',
],
'active' => TRUE,
'necessary' => TRUE,
],
'personalization_storage' => [
'category_id' => CategoryId::new()->toString(),
Expand All @@ -39,6 +40,7 @@
'en' => 'Personalization cookies',
],
'active' => TRUE,
'necessary' => FALSE,
],
'security_storage' => [
'category_id' => CategoryId::new()->toString(),
Expand All @@ -48,6 +50,7 @@
'en' => 'Security cookies',
],
'active' => TRUE,
'necessary' => FALSE,
],
'ad_storage' => [
'category_id' => CategoryId::new()->toString(),
Expand All @@ -57,6 +60,7 @@
'en' => 'Ad cookies',
],
'active' => TRUE,
'necessary' => FALSE,
],
'analytics_storage' => [
'category_id' => CategoryId::new()->toString(),
Expand All @@ -66,6 +70,7 @@
'en' => 'Analytics cookies',
],
'active' => TRUE,
'necessary' => FALSE,
],
];

Expand Down
109 changes: 109 additions & 0 deletions src/Application/Statistics/ProjectStatisticsCalculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
namespace App\Application\Statistics;

use DateTimeImmutable;
use App\ReadModel\Category\CategoryView;
use App\ReadModel\Consent\ConsentTotalsView;
use App\ReadModel\Category\AllCategoriesQuery;
use App\ReadModel\Consent\LastConsentDateView;
use App\ReadModel\Project\ProjectCookieTotalsView;
use App\ReadModel\Consent\CalculateLastConsentDatesQuery;
use App\ReadModel\Project\CalculateProjectCookieTotalsQuery;
use App\ReadModel\Consent\ScrollThroughConsentsPerPeriodQuery;
use App\ReadModel\Consent\CalculateConsentTotalsPerPeriodQuery;
use SixtyEightPublishers\ArchitectureBundle\Bus\QueryBusInterface;
use SixtyEightPublishers\ArchitectureBundle\ReadModel\Query\Batch;

final class ProjectStatisticsCalculator implements ProjectStatisticsCalculatorInterface
{
Expand Down Expand Up @@ -71,6 +75,111 @@ public function calculateConsentPeriodStatistics(array $projectIds, DateTimeImmu
return $result;
}

/**
* {@inheritDoc}
*/
public function calculatePositiveConsentPeriodStatistics(array $projectIds, DateTimeImmutable $startDate, DateTimeImmutable $endDate): MultiProjectConsentPeriodStatistics
{
$diff = $startDate->diff($endDate);
$previousEndDate = $startDate->modify('-1 second');
$previousStartDate = $previousEndDate->sub($diff);

// fill values
$consentStatistics = array_fill_keys($projectIds, [
'total' => [
'previousPositive' => 0,
'previousNegative' => 0,
'currentPositive' => 0,
'currentNegative' => 0,
],
'unique' => [
'previousPositive' => 0,
'previousNegative' => 0,
'currentPositive' => 0,
'currentNegative' => 0,
],
]);

// consent counter
$batchSize = 100;
$watched = $userIdentifiers = [];
$sumConsents = static function (array $consents, bool $positivity) use (&$watched): int {
return count(
array_filter(
$consents,
static fn (bool $consent, string $storageName): bool => $consent === $positivity && in_array($storageName, $watched, TRUE),
ARRAY_FILTER_USE_BOTH
),
);
};

// find watched categories
foreach ($this->queryBus->dispatch(AllCategoriesQuery::create()) as $categoryView) {
assert($categoryView instanceof CategoryView);

if (!$categoryView->necessary) {
$watched[] = $categoryView->code->value();
}
}

// the previous period
foreach ($this->queryBus->dispatch(ScrollThroughConsentsPerPeriodQuery::create($projectIds, $previousStartDate, $previousEndDate)->withBatchSize($batchSize)) as $batch) {
assert($batch instanceof Batch);

foreach ($batch->results() as $row) {
$consentStatistics[$row['projectId']]['total']['previousPositive'] += $sumConsents($row['consents'], TRUE);
$consentStatistics[$row['projectId']]['total']['previousNegative'] += $sumConsents($row['consents'], FALSE);

if (!isset($userIdentifiers[$row['userIdentifier']])) {
$consentStatistics[$row['projectId']]['unique']['previousPositive'] += $sumConsents($row['consents'], TRUE);
$consentStatistics[$row['projectId']]['unique']['previousNegative'] += $sumConsents($row['consents'], FALSE);
$userIdentifiers[$row['userIdentifier']] = TRUE;
}
}
}

// the current period
$userIdentifiers = [];

foreach ($this->queryBus->dispatch(ScrollThroughConsentsPerPeriodQuery::create($projectIds, $startDate, $endDate)->withBatchSize($batchSize)) as $batch) {
assert($batch instanceof Batch);

foreach ($batch->results() as $row) {
$consentStatistics[$row['projectId']]['total']['currentPositive'] += $sumConsents($row['consents'], TRUE);
$consentStatistics[$row['projectId']]['total']['currentNegative'] += $sumConsents($row['consents'], FALSE);

if (!isset($userIdentifiers[$row['userIdentifier']])) {
$consentStatistics[$row['projectId']]['unique']['currentPositive'] += $sumConsents($row['consents'], TRUE);
$consentStatistics[$row['projectId']]['unique']['currentNegative'] += $sumConsents($row['consents'], FALSE);
$userIdentifiers[$row['userIdentifier']] = TRUE;
}
}
}

// build result
$result = MultiProjectConsentPeriodStatistics::create();

foreach ($consentStatistics as $projectId => $statistic) {
$previousTotal = $statistic['total']['previousPositive'] + $statistic['total']['previousNegative'];
$currentTotal = $statistic['total']['currentPositive'] + $statistic['total']['currentNegative'];
$previousUnique = $statistic['unique']['previousPositive'] + $statistic['unique']['previousNegative'];
$currentUnique = $statistic['unique']['currentPositive'] + $statistic['unique']['currentNegative'];

$result = $result->withStatistics($projectId, ConsentPeriodStatistics::create(
PeriodStatistics::create(
(int) round(0 === $previousTotal ? 0 : $statistic['total']['previousPositive'] / $previousTotal * 100),
(int) round(0 === $currentTotal ? 0 : $statistic['total']['currentPositive'] / $currentTotal * 100)
),
PeriodStatistics::create(
(int) round(0 === $previousUnique ? 0 : $statistic['unique']['previousPositive'] / $previousUnique * 100),
(int) round(0 === $currentUnique ? 0 : $statistic['unique']['currentPositive'] / $currentUnique * 100)
)
));
}

return $result;
}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ interface ProjectStatisticsCalculatorInterface
*/
public function calculateConsentPeriodStatistics(array $projectIds, DateTimeImmutable $startDate, DateTimeImmutable $endDate): MultiProjectConsentPeriodStatistics;

/**
* @param array $projectIds
* @param \DateTimeImmutable $startDate
* @param \DateTimeImmutable $endDate
*
* @return \App\Application\Statistics\MultiProjectConsentPeriodStatistics
*/
public function calculatePositiveConsentPeriodStatistics(array $projectIds, DateTimeImmutable $startDate, DateTimeImmutable $endDate): MultiProjectConsentPeriodStatistics;

/**
* @param string[] $projectIds
*
Expand Down
33 changes: 32 additions & 1 deletion src/Domain/Category/Category.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use App\Domain\Category\Event\CategoryNameUpdated;
use App\Domain\Category\Command\CreateCategoryCommand;
use App\Domain\Category\Command\UpdateCategoryCommand;
use App\Domain\Category\Event\CategoryNecessaryChanged;
use App\Domain\Category\Event\CategoryActiveStateChanged;
use SixtyEightPublishers\ArchitectureBundle\Domain\ValueObject\AggregateId;
use SixtyEightPublishers\ArchitectureBundle\Domain\Aggregate\AggregateRootInterface;
Expand All @@ -33,6 +34,8 @@ final class Category implements AggregateRootInterface

private bool $active;

private bool $necessary;

private Collection $translations;

/**
Expand All @@ -48,11 +51,12 @@ public static function create(CreateCategoryCommand $command, CheckCodeUniquenes
$categoryId = NULL !== $command->categoryId() ? CategoryId::fromString($command->categoryId()) : CategoryId::new();
$code = Code::fromValidCode($command->code());
$active = $command->active();
$necessary = $command->necessary();
$names = $command->names();

$checkCodeUniqueness($categoryId, $code);

$category->recordThat(CategoryCreated::create($categoryId, $code, $active, $names));
$category->recordThat(CategoryCreated::create($categoryId, $code, $active, $necessary, $names));

return $category;
}
Expand All @@ -73,6 +77,10 @@ public function update(UpdateCategoryCommand $command, CheckCodeUniquenessInterf
$this->changeActiveState($command->active());
}

if (NULL !== $command->necessary()) {
$this->changeNecessary($command->necessary());
}

if (NULL !== $command->names()) {
foreach ($command->names() as $locale => $name) {
$this->changeName(Locale::fromValue($locale), Name::fromValue($name));
Expand Down Expand Up @@ -115,6 +123,18 @@ public function changeActiveState(bool $active): void
}
}

/**
* @param bool $necessary
*
* @return void
*/
public function changeNecessary(bool $necessary): void
{
if ($this->necessary !== $necessary) {
$this->recordThat(CategoryNecessaryChanged::create($this->id, $necessary));
}
}

/**
* @param \App\Domain\Shared\ValueObject\Locale $locale
* @param \App\Domain\Category\ValueObject\Name $name
Expand All @@ -141,6 +161,7 @@ protected function whenCategoryCreated(CategoryCreated $event): void
$this->createdAt = $event->createdAt();
$this->code = $event->code();
$this->active = $event->active();
$this->necessary = $event->necessary();
$this->translations = new ArrayCollection();

foreach ($event->names() as $locale => $name) {
Expand Down Expand Up @@ -168,6 +189,16 @@ protected function whenCategoryActiveStateChanged(CategoryActiveStateChanged $ev
$this->active = $event->active();
}

/**
* @param \App\Domain\Category\Event\CategoryNecessaryChanged $event
*
* @return void
*/
protected function whenCategoryNecessaryChanged(CategoryNecessaryChanged $event): void
{
$this->necessary = $event->necessary();
}

/**
* @param \App\Domain\Category\Event\CategoryNameUpdated $event
*
Expand Down
12 changes: 11 additions & 1 deletion src/Domain/Category/Command/CreateCategoryCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@ final class CreateCategoryCommand extends AbstractCommand
* @param string $code
* @param array $names
* @param bool $active
* @param bool $necessary
* @param string|NULL $categoryId
*
* @return static
*/
public static function create(string $code, array $names, bool $active, ?string $categoryId = NULL): self
public static function create(string $code, array $names, bool $active, bool $necessary, ?string $categoryId = NULL): self
{
return self::fromParameters([
'code' => $code,
'names' => $names,
'active' => $active,
'necessary' => $necessary,
'category_id' => $categoryId,
]);
}
Expand Down Expand Up @@ -50,6 +52,14 @@ public function active(): bool
return $this->getParam('active');
}

/**
* @return bool
*/
public function necessary(): bool
{
return $this->getParam('necessary');
}

/**
* @return string|NULL
*/
Expand Down
Loading

0 comments on commit 7973a3a

Please sign in to comment.