Mocks startFetch()/resolveFetch() instead of fetch(), and splits the
old single failure test into separate start-throws and
resolve-throws cases since those are now distinct call sites.
aggregate() now fires every configured provider's startFetch() in one
pass before resolving any of them, so wall-clock cost is roughly
max(providers) instead of sum(providers). Partial-failure isolation
and per-date summation behavior are unchanged.
startFetch() resolves the user id and fires the first events page,
returning a handle without reading it; resolveFetch() continues the
existing do/while pagination starting from that first response.
Pagination itself stays sequential within this provider since each
page depends on the previous one - parallelism here only spans across
providers, not within GitLab's own pages.
startFetch() fires the GraphQL POST and returns the response without
reading it; resolveFetch() does the .toArray()/parse/log work that
used to happen inline right after the request.
Splits the provider contract into a non-blocking startFetch() that
fires the HTTP request(s) and a resolveFetch() that reads the result,
so callers can fire every provider's request before blocking on any
of them. Symfony HttpClient is already async under the hood -
request() doesn't block until you read the response - so this needs
no new dependency.
Providers were moved into the App\Service\Provider namespace, but
services.yaml still tagged/configured them under the old App\Service
FQCNs, so the _instanceof conditional never matched and the container
failed to compile.
Move all provider-related classes, enums, interface and trait into
App\Service\Provider; move SvgRenderer into App\Service\Renderer.
ContributionAggregator stays at the Service root as the orchestrator.
Test namespaces and use statements updated to match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Multi-size favicon.ico (16×16, 32×32, 48×48) and favicon.png built from
a 5×5 GitHub-green contribution graph grid on a dark #0d1117 background.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>