Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public function toBroadcast(object $event): BroadcastEvent
payload: $event->event->getPayload(),
),
'timestamp' => $event->event->getTimestamp(),
'searchable_text' => $this->eventTypeMapper->toSearchableText(
type: $event->event->getType(),
payload: $event->event->getPayload(),
),
],
);
}
Expand Down
3 changes: 0 additions & 3 deletions app/modules/Events/Application/EventsBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use App\Application\Broadcasting\EventMapperRegistryInterface;
use App\Application\Persistence\DriverEnum;
use Cycle\Database\DatabaseInterface;
use Cycle\ORM\EntityManagerInterface;
use Cycle\ORM\ORMInterface;
use Cycle\ORM\Select;
use Modules\Events\Application\Broadcasting\EventsWasClearMapper;
Expand Down Expand Up @@ -38,10 +37,8 @@ public function defineSingletons(): array
},
EventRepository::class => static fn(
ORMInterface $orm,
EntityManagerInterface $manager,
DatabaseInterface $db,
): EventRepository => new EventRepository(
em: $manager,
db: $db,
select: new Select($orm, Event::class),
),
Expand Down
24 changes: 15 additions & 9 deletions app/modules/Events/Integration/CycleOrm/EventRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Modules\Events\Integration\CycleOrm;

use Cycle\Database\DatabaseInterface;
use Cycle\ORM\EntityManagerInterface;
use Cycle\ORM\Select;
use Cycle\ORM\Select\Repository;
use Modules\Events\Domain\Event;
Expand All @@ -18,7 +17,6 @@
final class EventRepository extends Repository implements EventRepositoryInterface
{
public function __construct(
private readonly EntityManagerInterface $em,
private readonly DatabaseInterface $db,
Select $select,
) {
Expand All @@ -27,14 +25,22 @@ public function __construct(

public function store(Event $event): bool
{
if (($found = $this->findByPK($event->getUuid())) !== null) {
$found->setPayload($event->getPayload());
$this->em->persist($found);
} else {
$this->em->persist($event);
}
$payload = (string) $event->getPayload();

$this->em->run();
try {
$this->db->insert(Event::TABLE_NAME)->values([
Event::UUID => (string) $event->getUuid(),
Event::TYPE => $event->getType(),
Event::PAYLOAD => $payload,
Event::TIMESTAMP => (string) $event->getTimestamp(),
Event::PROJECT => $event->getProject() !== null ? (string) $event->getProject() : null,
])->run();
} catch (\Throwable) {
$this->db->update(Event::TABLE_NAME)
->where(Event::UUID, (string) $event->getUuid())
->values([Event::PAYLOAD => $payload])
->run();
}

return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
new OA\Property(property: 'type', type: 'string'),
new OA\Property(property: 'payload', description: 'Event payload based on type', type: 'object'),
new OA\Property(property: 'timestamp', type: 'float', example: 1630540800.12312),
new OA\Property(property: 'searchable_text', description: 'Concatenated searchable content', type: 'string'),
],
)]
final class EventPreviewResource extends JsonResource
Expand All @@ -42,6 +43,10 @@ protected function mapData(): array|\JsonSerializable
payload: $this->data->getPayload(),
) ?? $this->data->getPayload(),
'timestamp' => $this->data->getTimestamp(),
'searchable_text' => $this->mapper?->toSearchableText(
type: $this->data->getType(),
payload: $this->data->getPayload(),
) ?? '',
];
}
}
7 changes: 7 additions & 0 deletions app/modules/HttpDumps/Application/HttpDumpsBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
use Cycle\ORM\ORMInterface;
use Cycle\ORM\Select;
use Modules\HttpDumps\EventHandler;
use Modules\HttpDumps\Application\Mapper\EventTypeMapper;
use Modules\HttpDumps\Application\Storage\AttachmentStorage;
use Modules\HttpDumps\Domain\AttachmentFactoryInterface;
use Modules\HttpDumps\Domain\AttachmentRepositoryInterface;
use Modules\HttpDumps\Domain\AttachmentStorageInterface;
use Modules\HttpDumps\Domain\Attachment;
use Modules\HttpDumps\Integration\CycleOrm\AttachmentRepository;
use App\Application\Event\EventTypeRegistryInterface;
use Psr\Container\ContainerInterface;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Core\FactoryInterface;
Expand Down Expand Up @@ -58,4 +60,9 @@ public function defineSingletons(): array
},
];
}

public function boot(EventTypeRegistryInterface $registry): void
{
$registry->register('http-dump', new EventTypeMapper());
}
}
26 changes: 26 additions & 0 deletions app/modules/HttpDumps/Application/Mapper/EventTypeMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Modules\HttpDumps\Application\Mapper;

use App\Application\Event\EventTypeMapperInterface;

final readonly class EventTypeMapper implements EventTypeMapperInterface
{
public function toPreview(string $type, array|\JsonSerializable $payload): array|\JsonSerializable
{
return $payload;
}

public function toSearchableText(string $type, array|\JsonSerializable $payload): string
{
$data = $payload instanceof \JsonSerializable ? $payload->jsonSerialize() : $payload;

return \implode(' ', \array_filter([
$data['host'] ?? null,
$data['request']['method'] ?? null,
$data['request']['uri'] ?? null,
]));
}
}
20 changes: 20 additions & 0 deletions app/modules/Inspector/Application/Mapper/EventTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,24 @@ public function toPreview(string $type, array|\JsonSerializable $payload): array
$transaction,
];
}

public function toSearchableText(string $type, array|\JsonSerializable $payload): string
{
$data = $payload instanceof \JsonSerializable ? $payload->jsonSerialize() : $payload;

$parts = [];

foreach ($data as $block) {
if (!is_array($block)) {
continue;
}
foreach (['model', 'name', 'type', 'host'] as $key) {
if (isset($block[$key]) && \is_string($block[$key])) {
$parts[] = $block[$key];
}
}
}

return \implode(' ', $parts);
}
}
26 changes: 26 additions & 0 deletions app/modules/Monolog/Application/Mapper/EventTypeMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Modules\Monolog\Application\Mapper;

use App\Application\Event\EventTypeMapperInterface;

final readonly class EventTypeMapper implements EventTypeMapperInterface
{
public function toPreview(string $type, array|\JsonSerializable $payload): array|\JsonSerializable
{
return $payload;
}

public function toSearchableText(string $type, array|\JsonSerializable $payload): string
{
$data = $payload instanceof \JsonSerializable ? $payload->jsonSerialize() : $payload;

return \implode(' ', \array_filter([
$data['message'] ?? null,
$data['channel'] ?? null,
$data['level_name'] ?? null,
]));
}
}
17 changes: 17 additions & 0 deletions app/modules/Monolog/Application/MonologEventBootloader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Modules\Monolog\Application;

use Modules\Monolog\Application\Mapper\EventTypeMapper;
use App\Application\Event\EventTypeRegistryInterface;
use Spiral\Boot\Bootloader\Bootloader;

final class MonologEventBootloader extends Bootloader
{
public function boot(EventTypeRegistryInterface $registry): void
{
$registry->register('monolog', new EventTypeMapper());
}
}
16 changes: 16 additions & 0 deletions app/modules/Profiler/Application/Mapper/EventTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,20 @@ public function toPreview(string $type, array|\JsonSerializable $payload): array
'date' => $data['date'],
];
}

public function toSearchableText(string $type, array|\JsonSerializable $payload): string
{
$data = $payload instanceof \JsonSerializable ? $payload->jsonSerialize() : $payload;

$parts = \array_filter([
$data['app_name'] ?? null,
$data['hostname'] ?? null,
]);

foreach (($data['tags'] ?? []) as $key => $value) {
$parts[] = \is_string($key) ? "{$key}:{$value}" : $value;
}

return \implode(' ', $parts);
}
}
24 changes: 24 additions & 0 deletions app/modules/Sentry/Application/Mapper/EventTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,30 @@ public function toPreview(string $type, array|\JsonSerializable $payload): array
];
}

public function toSearchableText(string $type, array|\JsonSerializable $payload): string
{
$data = $payload instanceof \JsonSerializable ? $payload->jsonSerialize() : $payload;

$parts = \array_filter([
$data['message'] ?? null,
$data['level'] ?? null,
$data['environment'] ?? null,
$data['server_name'] ?? null,
$data['platform'] ?? null,
]);

foreach (($data['exception']['values'] ?? []) as $e) {
if (isset($e['type'])) {
$parts[] = $e['type'];
}
if (isset($e['value'])) {
$parts[] = $e['value'];
}
}

return \implode(' ', $parts);
}

public function limitExceptionFramesNumber(array|null $exception, int $max = 3): array|null
{
if ($exception === null) {
Expand Down
19 changes: 19 additions & 0 deletions app/modules/Smtp/Application/Mapper/EventTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,23 @@ public function toPreview(string $type, array|\JsonSerializable $payload): array
'to' => $data['to'],
];
}

public function toSearchableText(string $type, array|\JsonSerializable $payload): string
{
$data = $payload instanceof \JsonSerializable ? $payload->jsonSerialize() : $payload;

$parts = \array_filter([
$data['subject'] ?? null,
]);

foreach (($data['from'] ?? []) as $address) {
$parts[] = \is_array($address) ? ($address['email'] ?? $address['address'] ?? '') : (string) $address;
}

foreach (($data['to'] ?? []) as $address) {
$parts[] = \is_array($address) ? ($address['email'] ?? $address['address'] ?? '') : (string) $address;
}

return \implode(' ', $parts);
}
}
9 changes: 9 additions & 0 deletions app/src/Application/Event/EventTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ public function toPreview(string $type, array|\JsonSerializable $payload): array
return $this->mappers[$type]->toPreview($type, $payload);
}

public function toSearchableText(string $type, array|\JsonSerializable $payload): string
{
if (!isset($this->mappers[$type])) {
return '';
}

return $this->mappers[$type]->toSearchableText($type, $payload);
}

public function register(string $type, EventTypeMapperInterface $mapper): void
{
if (isset($this->mappers[$type])) {
Expand Down
2 changes: 2 additions & 0 deletions app/src/Application/Event/EventTypeMapperInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@
interface EventTypeMapperInterface
{
public function toPreview(string $type, array|\JsonSerializable $payload): array|\JsonSerializable;

public function toSearchableText(string $type, array|\JsonSerializable $payload): string;
}
2 changes: 2 additions & 0 deletions app/src/Application/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Modules\Projects\Application\ProjectBootloader;
use Modules\Ray\Application\RayBootloader;
use Modules\HttpDumps\Application\HttpDumpsBootloader;
use Modules\Monolog\Application\MonologEventBootloader;
use Modules\Sentry\Application\SentryBootloader;
use Modules\Smtp\Application\SmtpBootloader;
use Modules\VarDumper\Application\VarDumperBootloader;
Expand Down Expand Up @@ -99,6 +100,7 @@ protected function defineBootloaders(): array
VarDumperBootloader::class,
RayBootloader::class,
HttpDumpsBootloader::class,
MonologEventBootloader::class,
ProfilerBootloader::class,
PersistenceBootloader::class,
WebhooksBootloader::class,
Expand Down
47 changes: 47 additions & 0 deletions tests/Unit/Modules/Events/Domain/CacheEventRepositoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Tests\Unit\Modules\Events\Domain;

use App\Application\Domain\ValueObjects\Json;
use Modules\Events\Domain\EventRepositoryInterface;
use Tests\DatabaseTestCase;

Expand All @@ -27,6 +28,52 @@ public function testStoreEvent(): void
$this->assertCount(1, \iterator_to_array($this->repository->findAll()));
}

public function testUpsertNewEvent(): void
{
$event = $this->createEvent(type: 'sentry');

$this->assertCount(1, \iterator_to_array($this->repository->findAll()));

$found = $this->repository->findByPK($event->getUuid());
$this->assertNotNull($found);
$this->assertSame('sentry', $found->getType());
}

public function testUpsertExistingEventUpdatesPayload(): void
{
$event = $this->createEvent(type: 'sentry');

$this->assertCount(1, \iterator_to_array($this->repository->findAll()));

$newPayload = new Json(['updated' => true, 'message' => 'changed']);
$event->setPayload($newPayload);
$this->repository->store($event);

$this->assertCount(1, \iterator_to_array($this->repository->findAll()));

$this->cleanIdentityMap();
$found = $this->repository->findByPK($event->getUuid());
$this->assertNotNull($found);
$this->assertSame(['updated' => true, 'message' => 'changed'], $found->getPayload()->jsonSerialize());
}

public function testUpsertDoesNotDuplicateOnSameUuid(): void
{
$event = $this->createEvent(type: 'monolog');

$event->setPayload(new Json(['attempt' => 2]));
$this->repository->store($event);

$event->setPayload(new Json(['attempt' => 3]));
$this->repository->store($event);

$this->assertCount(1, \iterator_to_array($this->repository->findAll()));

$this->cleanIdentityMap();
$found = $this->repository->findByPK($event->getUuid());
$this->assertSame(['attempt' => 3], $found->getPayload()->jsonSerialize());
}

public function testDeleteByType(): void
{
$this->createEvent(type: 'foo');
Expand Down
Loading