From 0204e5acf8977ae5fd148e27287faf89ae938bea Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 03:47:54 +0000 Subject: [PATCH 1/2] feat: Add overall ETA calculation for progress tracking This adds a `getOverallEstimatedTimeRemaining` method to calculate the global ETA of a task based on the average progress and the earliest start time of all local instances. This provides concrete value by allowing applications to display remaining time for complex multi-part jobs, completing the existing local ETA functionality. Co-authored-by: insign <1113045+insign@users.noreply.github.com> --- README.md | 2 + src/Progressable.php | 38 +++++++++++++++++++ tests/OverallEtaTest.php | 77 ++++++++++++++++++++++++++++++++++++++ tests/ProgressableTest.php | 1 + 4 files changed, 118 insertions(+) create mode 100644 tests/OverallEtaTest.php diff --git a/README.md b/README.md index 03f57d7..e317c8e 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ class MyFirstTask | `getOverallProgress(?int $precision)` | Get average progress of all instances | | `resetOverallProgress()` | Clear all progress data for the group | | `removeLocalFromOverall()` | Remove this instance from overall calculation | +| `getEstimatedTimeRemaining()` | Get estimated time remaining in seconds for this instance | +| `getOverallEstimatedTimeRemaining()` | Get estimated time remaining in seconds for the overall progress | #### Status Checks diff --git a/src/Progressable.php b/src/Progressable.php index f3cd5c0..4fe078d 100644 --- a/src/Progressable.php +++ b/src/Progressable.php @@ -267,6 +267,43 @@ public function isOverallComplete(): bool { return true; } + /** + * Get the estimated time remaining in seconds for the overall progress. + */ + public function getOverallEstimatedTimeRemaining(): ?int { + $overallProgress = $this->getOverallProgress(); + + if ($overallProgress >= 100) { + return 0; + } + + $progressData = $this->getOverallProgressData(); + + if (empty($progressData)) { + return null; + } + + $startTimes = array_filter(array_column($progressData, 'start_time'), function ($time) { + return $time !== null; + }); + + if (empty($startTimes) || $overallProgress <= 0) { + return null; + } + + $minStartTime = min($startTimes); + $elapsed = Carbon::now()->timestamp - $minStartTime; + + if ($elapsed <= 0) { + return null; + } + + $rate = $overallProgress / $elapsed; + $remainingProgress = 100 - $overallProgress; + + return (int) round($remainingProgress / $rate); + } + /** * Get the estimated time remaining in seconds. */ @@ -671,6 +708,7 @@ public function toArray(): array { 'is_complete' => $this->isComplete(), 'is_overall_complete' => $hasUniqueName ? $this->isOverallComplete() : null, 'estimated_time_remaining' => $hasUniqueName ? $this->getEstimatedTimeRemaining() : null, + 'overall_estimated_time_remaining' => $hasUniqueName ? $this->getOverallEstimatedTimeRemaining() : null, 'message' => $this->getStatusMessage(), 'metadata' => $this->getMetadata(), 'total_steps' => $this->getTotalSteps(), diff --git a/tests/OverallEtaTest.php b/tests/OverallEtaTest.php new file mode 100644 index 0000000..033ee83 --- /dev/null +++ b/tests/OverallEtaTest.php @@ -0,0 +1,77 @@ +testId = uniqid('test_', true); + + // Reset properties + $this->progress = 0; + unset($this->overallUniqueName); + } + + public function test_overall_eta_is_null_initially(): void { + $this->setOverallUniqueName('test_overall_eta_init_'.$this->testId); + $this->assertNull($this->getOverallEstimatedTimeRemaining()); + } + + public function test_overall_eta_calculation(): void { + Carbon::setTestNow(Carbon::now()); + + $uniqueName = 'test_overall_eta_calc_'.$this->testId; + $this->setOverallUniqueName($uniqueName); + + // First process starts at T0 + $this->setLocalKey('process_1'); + $this->setLocalProgress(0); + + // Second process starts at T+5s + Carbon::setTestNow(Carbon::now()->addSeconds(5)); + + $obj2 = new class { + use Progressable; + }; + $obj2->setOverallUniqueName($uniqueName); + $obj2->setLocalKey('process_2'); + $obj2->setLocalProgress(0); + + // Advance to T+10s + Carbon::setTestNow(Carbon::now()->addSeconds(5)); + + // Set process 1 to 20% and process 2 to 0% -> overall = 10% + // Min start time is T0, so elapsed is 10s. + // Rate = 10% / 10s = 1% per second. + // Remaining = 90%. ETA = 90s. + $this->setLocalProgress(20); + + $this->assertEquals(90, $this->getOverallEstimatedTimeRemaining()); + $this->assertEquals(90, $obj2->getOverallEstimatedTimeRemaining()); + } + + public function test_overall_eta_is_zero_when_complete(): void { + $uniqueName = 'test_overall_eta_complete_'.$this->testId; + + $this->setOverallUniqueName($uniqueName); + $this->setLocalKey('process_1'); + $this->setLocalProgress(100); + + $obj2 = new class { + use Progressable; + }; + $obj2->setOverallUniqueName($uniqueName); + $obj2->setLocalKey('process_2'); + $obj2->setLocalProgress(100); + + $this->assertEquals(0, $this->getOverallEstimatedTimeRemaining()); + } +} diff --git a/tests/ProgressableTest.php b/tests/ProgressableTest.php index 545b26b..5d3bb30 100644 --- a/tests/ProgressableTest.php +++ b/tests/ProgressableTest.php @@ -587,6 +587,7 @@ public function test_to_array_without_unique_name(): void { 'is_complete' => false, 'is_overall_complete' => null, 'estimated_time_remaining' => null, + 'overall_estimated_time_remaining' => null, 'message' => 'Halfway there', 'metadata' => ['foo' => 'bar'], 'total_steps' => 10, From a41969ba40ad6ff8f753dd4ba60bedc59552a585 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 00:53:44 +0000 Subject: [PATCH 2/2] style: Fix pint styling issues in IsOverallCompleteBugTest.php Running `vendor/bin/pint` fixed missing blank lines and parenthesis warnings in `tests/IsOverallCompleteBugTest.php` that was causing the CI build to fail on Code Style check. Co-authored-by: insign <1113045+insign@users.noreply.github.com> --- tests/IsOverallCompleteBugTest.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/IsOverallCompleteBugTest.php b/tests/IsOverallCompleteBugTest.php index 601ad79..f3d97ab 100644 --- a/tests/IsOverallCompleteBugTest.php +++ b/tests/IsOverallCompleteBugTest.php @@ -1,24 +1,22 @@ setOverallUniqueName('test-overall-complete-bug'); $a->setLocalKey('a'); $a->setLocalProgress(100); - $b = new DummyProgressableOverallComplete(); + $b = new DummyProgressableOverallComplete; $b->setOverallUniqueName('test-overall-complete-bug'); $b->setLocalKey('b'); $b->setLocalProgress(99.4);