Skip to content
Open
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
25 changes: 25 additions & 0 deletions src/Event/Api/EventsApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,31 @@ public function getEventsForSourceLegacy(DateTimeInterface $observationTime, str
}
}

/** {@inheritdoc} */
public function getEventsForSource(DateTimeInterface $observationTime, string $source): array
{
$formattedTime = $observationTime->format('Y-m-d\TH:i:s\Z');
$encodedTime = urlencode($formattedTime);

$url = "/api/v1/events/{$source}/observation/{$encodedTime}";

$this->sentry->setContext('EventsApi', [
'endpoint' => $url,
]);

try {
$response = $this->client->request('GET', $url);
return $this->parseResponse($response);
} catch (\Throwable $e) {
$this->sentry->setContext('EventsApi', [
'error' => $e->getMessage(),
]);
$exception = new EventsApiException("Failed to fetch events for source: " . $e->getMessage(), 0, $e);
$this->sentry->capture($exception);
throw $exception;
}
}

/** {@inheritdoc} */
public function getEventsInRange(int $fromTimestamp, int $toTimestamp, array $paths): array
{
Expand Down
11 changes: 11 additions & 0 deletions src/Event/Api/EventsApiInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ interface EventsApiInterface {
*/
public function getEventsForSourceLegacy(DateTimeInterface $observationTime, string $source): array;

/**
* Get events for a specific source via the v1 endpoint
* (events/{source}/observation/{ISO-8601-Z time}).
*
* @param DateTimeInterface $observationTime The observation time
* @param string $source The data source (e.g. "HEK", "CCMC", "RHESSI")
* @return array Array of event data
* @throws EventsApiException on API errors or unexpected responses
*/
public function getEventsForSource(DateTimeInterface $observationTime, string $source): array;

/**
* Get events within a time range for given selection paths
*
Expand Down
20 changes: 16 additions & 4 deletions src/Module/SolarEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Helioviewer\Api\Module\BaseModule;
use Helioviewer\Api\Module\ModuleInterface;
use Helioviewer\Api\Sentry\Sentry;
use Helioviewer\Api\Event\Api\EventsApi;
use Helioviewer\Api\Event\Api\EventsApiException;

class Module_SolarEvents extends BaseModule implements ModuleInterface {
Expand Down Expand Up @@ -191,8 +192,16 @@ public function importEvents() {
public function events() {
$observationTime = new DateTimeImmutable($this->_params['startTime']);

// Output format selector: 'tree' (legacy nested categories, default)
// or 'flat' (new v1 per-source endpoint). Anything else falls back to
// 'tree' for backwards compatibility.
$format = $this->_options['format'] ?? 'tree';
if ($format !== 'flat') {
$format = 'tree';
}

// All event sources supported by the Events API
$allSources = ['CCMC', 'HEK', 'RHESSI'];
$allSources = EventsApi::VALID_SOURCES;

// Determine which sources to query
if (array_key_exists('sources', $this->_options)) {
Expand All @@ -206,7 +215,9 @@ public function events() {

foreach ($sources as $source) {
try {
$sourceData = $this->eventsApi()->getEventsForSourceLegacy($observationTime, $source);
$sourceData = ($format === 'flat')
? $this->eventsApi()->getEventsForSource($observationTime, $source)
: $this->eventsApi()->getEventsForSourceLegacy($observationTime, $source);
$data = array_merge($data, $sourceData);
} catch (EventsApiException $e) {
return $this->_sendResponse(500, 'Internal Server Error', 'Failed to fetch events from ' . $source);
Expand Down Expand Up @@ -268,10 +279,11 @@ public function getValidationRules(): array {
$expected = array(
'required' => array('startTime'),
'optional' => array('eventType', 'cacheOnly', 'force',
'ar_filter', 'sources'),
'ar_filter', 'sources', 'format'),
'bools' => array('cacheOnly','force','ar_filter'),
'dates' => array('startTime'),
'alphanumlist' => array('eventType', 'sources')
'alphanumlist' => array('eventType', 'sources'),
'choices' => array('format' => ['tree', 'flat']),
);
break;
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,17 @@ public function testItShouldReturnEmptyForEmptyTimestamps(): void

public function testItShouldFallBackToConfiguredChunkSizeWhenCallerPassesLessThanOne(): void
{
// 60 timestamps with chunkSize=0 -> defined config or fallback 50 -> 2 chunks (50 + 10)
// Generate enough timestamps to force 2 chunks at whatever the
// configured fallback chunksize is. If HV_EVENTS_API_EVENTS_PER_FRAME_CHUNKSIZE
// is defined (via Config.ini) the fallback uses it; otherwise 50.
$fallbackChunk = defined('HV_EVENTS_API_EVENTS_PER_FRAME_CHUNKSIZE')
? (int) HV_EVENTS_API_EVENTS_PER_FRAME_CHUNKSIZE
: 50;
$tail = 10;
$total = $fallbackChunk + $tail;

$timestamps = [];
for ($i = 0; $i < 60; $i++) {
for ($i = 0; $i < $total; $i++) {
$timestamps[] = "2024-01-15 " . sprintf('%02d:%02d:00', intdiv($i, 60), $i % 60);
}

Expand All @@ -70,11 +78,11 @@ public function testItShouldFallBackToConfiguredChunkSizeWhenCallerPassesLessTha
$this->mockClient->expects($this->exactly(2))
->method('request')
->withConsecutive(
['POST', '/helioviewer/events/frames_with_selections', $this->callback(function ($options) {
return count($options['json']['timestamps']) === 50;
['POST', '/helioviewer/events/frames_with_selections', $this->callback(function ($options) use ($fallbackChunk) {
return count($options['json']['timestamps']) === $fallbackChunk;
})],
['POST', '/helioviewer/events/frames_with_selections', $this->callback(function ($options) {
return count($options['json']['timestamps']) === 10;
['POST', '/helioviewer/events/frames_with_selections', $this->callback(function ($options) use ($tail) {
return count($options['json']['timestamps']) === $tail;
})]
)
->willReturn(new Response(200, [], json_encode($emptyResponse)));
Expand Down
Loading