From d599b4aa62e6c4dc487785942628841426b4b3c4 Mon Sep 17 00:00:00 2001 From: Kasim Necdet Percinel Date: Wed, 10 Jun 2026 15:38:37 +0000 Subject: [PATCH 1/4] bring new format field to events endpoint tobe able to eventsapi simple event search will simplify our system , step to eventually remove tree format event responses, which brings unneccessary complication to our codes --- src/Event/Api/EventsApi.php | 25 +++++++++++++++++++++++++ src/Event/Api/EventsApiInterface.php | 11 +++++++++++ src/Module/SolarEvents.php | 20 ++++++++++++++++---- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/Event/Api/EventsApi.php b/src/Event/Api/EventsApi.php index 78265246..0e7907c8 100644 --- a/src/Event/Api/EventsApi.php +++ b/src/Event/Api/EventsApi.php @@ -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 { diff --git a/src/Event/Api/EventsApiInterface.php b/src/Event/Api/EventsApiInterface.php index 3664caa8..b3c98f4c 100644 --- a/src/Event/Api/EventsApiInterface.php +++ b/src/Event/Api/EventsApiInterface.php @@ -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 * diff --git a/src/Module/SolarEvents.php b/src/Module/SolarEvents.php index e22dc7ec..91647da5 100644 --- a/src/Module/SolarEvents.php +++ b/src/Module/SolarEvents.php @@ -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 { @@ -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)) { @@ -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); @@ -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: From 7819653458d72b2fc652a9f072df28c80bf4a889 Mon Sep 17 00:00:00 2001 From: Kasim Necdet Percinel Date: Wed, 10 Jun 2026 20:26:20 +0000 Subject: [PATCH 2/4] fix config fragile test --- .../GetEventsForFramesWithSelectionsTest.php | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/unit_tests/events/api/GetEventsForFramesWithSelectionsTest.php b/tests/unit_tests/events/api/GetEventsForFramesWithSelectionsTest.php index 53bc802d..64d3a962 100644 --- a/tests/unit_tests/events/api/GetEventsForFramesWithSelectionsTest.php +++ b/tests/unit_tests/events/api/GetEventsForFramesWithSelectionsTest.php @@ -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); } @@ -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))); From 87fe7c1c2facc98a8a0a2d0f5decfda6f6127580 Mon Sep 17 00:00:00 2001 From: Kasim Necdet Percinel Date: Mon, 15 Jun 2026 18:17:21 +0000 Subject: [PATCH 3/4] fix events/tests with [] value , production error from sentry --- src/Validation/InputValidator.php | 5 +++-- tests/unit_tests/validation/LegacyEventStringTest.php | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Validation/InputValidator.php b/src/Validation/InputValidator.php index 85cb6a8b..e5ccd577 100644 --- a/src/Validation/InputValidator.php +++ b/src/Validation/InputValidator.php @@ -506,8 +506,9 @@ public static function checkLegacyEventString($required, &$params) if (isset($params[$req])) { $value = $params[$req]; - // Empty string is valid (no events selected) - if ($value === '') { + // Empty string OR empty brackets is valid (no events selected). + // Frontends that wrap selections in [...] send '[]' to mean none. + if ($value === '' || $value === '[]') { continue; } diff --git a/tests/unit_tests/validation/LegacyEventStringTest.php b/tests/unit_tests/validation/LegacyEventStringTest.php index b6ad59ae..a153e77a 100644 --- a/tests/unit_tests/validation/LegacyEventStringTest.php +++ b/tests/unit_tests/validation/LegacyEventStringTest.php @@ -305,10 +305,10 @@ public static function legacyEventStringProvider(): array false, 'Nested brackets' ], - 'invalid_only_brackets' => [ + 'valid_empty_brackets' => [ '[]', - false, - 'Empty brackets' + true, + 'Empty brackets are accepted as "no events selected" (frontends wrap selections in [...] and send [] for none)' ], ]; } From b507321f723c5a5af68ccbd72d4e67940a731cb0 Mon Sep 17 00:00:00 2001 From: Kasim Necdet Percinel Date: Tue, 16 Jun 2026 15:25:29 +0000 Subject: [PATCH 4/4] documentation changes after added for format=flat --- .../solar_features_and_events/events.rst | 57 ++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/docs/src/source/api/api_groups/solar_features_and_events/events.rst b/docs/src/source/api/api_groups/solar_features_and_events/events.rst index 4feb845b..f44e31b1 100644 --- a/docs/src/source/api/api_groups/solar_features_and_events/events.rst +++ b/docs/src/source/api/api_groups/solar_features_and_events/events.rst @@ -11,24 +11,33 @@ Returns a list of HEK events in the :ref:`helioviewer-event-format`. +===========+==========+========+======================+============================================================+ | startTime | Required | string | 2023-01-01T00:00:00Z | Specific time to get predictions for. | +-----------+----------+--------+----------------------+------------------------------------------------------------+ - | sources | Optional | string | HEK,DONKI | | Specify the external data sources to use for the request | + | sources | Optional | string | HEK,CCMC | | Specify the external data sources to use for the request | | | | | | | If not provided, all sources will be queried | - | | | | | | Current options are HEK and DONKI. | + | | | | | | Allowed values: HEK, CCMC, RHESSI. | + +-----------+----------+--------+----------------------+------------------------------------------------------------+ + | format | Optional | string | flat | | Output shape. ``tree`` (default) returns the legacy | + | | | | | | nested category/group structure described in | + | | | | | | :ref:`helioviewer-event-format`. ``flat`` returns the | + | | | | | | new v1 per-source response (one object per event with | + | | | | | | no category nesting). Allowed values: ``tree``, | + | | | | | | ``flat``. | +-----------+----------+--------+----------------------+------------------------------------------------------------+ -See :ref:`helioviewer-event-format` for the response format. +See :ref:`helioviewer-event-format` for the response format when ``format=tree`` +(the default). When ``format=flat`` is requested, the response is the raw v1 +events payload for each source, concatenated -- no nesting, no group keys. Event specific data conforms to the `HEK Event Specification `_ -Example: Get HEK Events for 2023-03-30 +Example: Get HEK Events for 2023-03-30 (tree, default) .. code-block:: - :caption: Example Query + :caption: Example Query (format=tree) - https://api.helioviewer.org/v2/events/?date=2023-03-30T00:00:00Z + https://api.helioviewer.org/v2/events/?startTime=2023-03-30T00:00:00Z&sources=HEK .. code-block:: - :caption: Example Response + :caption: Example Response (format=tree) [ { @@ -63,4 +72,38 @@ Example: Get HEK Events for 2023-03-30 ] }, ... + ] + +Example: Same request but with the new flat shape + +.. code-block:: + :caption: Example Query (format=flat) + + https://api.helioviewer.org/v2/events/?startTime=2023-03-30T00:00:00Z&sources=HEK&format=flat + +.. code-block:: + :caption: Example Response (format=flat) + + [ + { + "absnetcurrenthelicity": null, + "active": "true", + "area_atdiskcenter": 213057280, + "area_atdiskcenteruncert": null, + "area_raw": null, + "area_uncert": null, + "area_unit": "km2", + "ar_axislength": null, + "ar_compactnesscls": "", + "ar_lengthunit": "", + "ar_mcintoshcls": "HAX", + "ar_mtwilsoncls": "ALPHA", + "ar_neutrallength": null, + "concept": "Active Region", + "frm_name": "NOAA SWPC Observer", + "frm_institute": "NOAA-SWPC", + "pin": "AR", + ... + }, + ... ] \ No newline at end of file