feat: record application deleted event for statistics
This commit is contained in:
1 parent
3d4d92b5c4
commit
66527515ef
4 files changed
+231
No files matched your search
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Statistics\Collector;
|
||||
|
||||
use App\Entity\Application;
|
||||
use App\Entity\Assignment;
|
||||
use App\Entity\Disposition;
|
||||
use App\Entity\Teamer;
|
||||
use App\Enum\ApplicationDeletionOutcome;
|
||||
use App\Enum\StatisticsEventName;
|
||||
use App\Service\Statistics\CollectedStatisticsEvent;
|
||||
use App\Service\Statistics\StatisticsDimensions;
|
||||
use App\Service\Statistics\StatisticsFlush;
|
||||
|
||||
/**
|
||||
* An application leaving the table, and whether that was a loss.
|
||||
*
|
||||
* Applications are hard deleted with no cascade and no soft delete behind them, so until this
|
||||
* existed an application.created row had no counterpart and nothing but the audit log said
|
||||
* where the application went. Read from the flush rather than from the four controllers and
|
||||
* listeners that remove one, so every path is counted.
|
||||
*
|
||||
* The outcome is the point. Staffing somebody deletes their application too - the dispose
|
||||
* controller persists the placement and removes the application in the same flush - so
|
||||
* counting deletions alone would read every success as a lost application.
|
||||
*
|
||||
* Dimensions are resolved here, during onFlush: by postFlush the entity is detached and its
|
||||
* id nulled, so StatisticsRecorder could not resolve them from the subject.
|
||||
*/
|
||||
class ApplicationDeletedCollector implements StatisticsCollectorInterface
|
||||
{
|
||||
public function collect(StatisticsFlush $flush): iterable
|
||||
{
|
||||
$placements = $this->placementsCreatedIn($flush);
|
||||
|
||||
foreach ($flush->deletionsOf(Application::class) as $application) {
|
||||
$key = $this->key($application->getAssignment(), $application->getTeamer());
|
||||
$dispositionUuid = null !== $key ? ($placements[$key] ?? null) : null;
|
||||
|
||||
yield new CollectedStatisticsEvent(
|
||||
StatisticsEventName::APPLICATION_DELETED,
|
||||
$application,
|
||||
[
|
||||
'outcome' => (null !== $dispositionUuid
|
||||
? ApplicationDeletionOutcome::DISPOSED
|
||||
: ApplicationDeletionOutcome::REMOVED)->value,
|
||||
'previous_status' => $application->getStatus(),
|
||||
// The only handle that outlives the row - application_id points at a
|
||||
// deleted record - and the same value LoggingSubscriber writes to the
|
||||
// audit log, which is what makes the two joinable.
|
||||
'application_uuid' => $application->getUuid(),
|
||||
'disposition_uuid' => $dispositionUuid,
|
||||
],
|
||||
StatisticsDimensions::forApplication($application),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The placements created in this flush, by the teamer and assignment they are for.
|
||||
*
|
||||
* Keyed on uuids rather than ids or object identity: a disposition being inserted has no
|
||||
* id yet at this point, and the application it came from loses its own the moment the
|
||||
* delete commits. Disposition::__construct() copies the assignment and the teamer off the
|
||||
* application without keeping a reference back to it, so that pair is all there is to
|
||||
* match on - and a teamer holds at most one application per overlapping period, which is
|
||||
* what ApplicationValidator enforces.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function placementsCreatedIn(StatisticsFlush $flush): array
|
||||
{
|
||||
$placements = [];
|
||||
|
||||
foreach ($flush->insertionsOf(Disposition::class) as $disposition) {
|
||||
$key = $this->key($disposition->getAssignment(), $disposition->getTeamer());
|
||||
|
||||
if (null === $key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$placements[$key] = $disposition->getUuid();
|
||||
}
|
||||
|
||||
return $placements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null when either side is missing: two half-anonymous records must not match each other
|
||||
* on the strength of what they both lack.
|
||||
*/
|
||||
private function key(?Assignment $assignment, ?Teamer $teamer): ?string
|
||||
{
|
||||
if (null === $assignment || null === $teamer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sprintf('%s:%s', $assignment->getUuid(), $teamer->getUuid());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user