feat(strategypro): reverse-engineer flow — Outcomes pilot (submodule bump + tests)#3651
feat(strategypro): reverse-engineer flow — Outcomes pilot (submodule bump + tests)#3651gloriafolaron wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR advances the StrategyPro “reverse-engineer flow” Outcomes pilot by bumping the app/Plugins submodule pointer to the corresponding plugin work and adding unit coverage for the reverse flow’s read/adaptation logic (via WorkAdapter).
Changes:
- Bump
app/Pluginssubmodule to the StrategyPro plugin HEAD implementing the reverse flow read logic. - Add a focused unit test suite for
WorkAdapter::adapt()covering goal classification, hidden/provenance filtering outcomes, completeness messaging, and scope passthrough.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
app/Plugins (submodule) |
Updates StrategyPro plugin code version used by core (reverse-flow Outcomes pilot). |
tests/Unit/app/Plugins/StrategyPro/Services/WorkAdapterTest.php |
Adds 7 unit tests validating WorkAdapter adaptation output shape, classification rules, and completeness messaging. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| class WorkAdapterTest extends TestCase | ||
| { | ||
| public function test_empty_strategy_returns_empty_completeness(): void | ||
| { |
| $this->assertSame(1, $out['outcomes']['hiddenCount']); | ||
| } | ||
|
|
||
| public function test_sparse_level_when_one_or_two_candidates(): void |
| class WorkAdapterTest extends TestCase | ||
| { | ||
| public function test_empty_strategy_returns_empty_completeness(): void |
| class ReverseStageTest extends TestCase | ||
| { | ||
| public function test_impact_can_never_be_seeded(): void |
| class WorkAdapterTest extends TestCase | ||
| { | ||
| public function test_empty_strategy_returns_empty_completeness(): void | ||
| { |
| class ReverseStageTest extends TestCase | ||
| { | ||
| public function test_impact_can_never_be_seeded(): void | ||
| { |
| class WorkAdapterTest extends TestCase | ||
| { | ||
| public function test_empty_strategy_returns_empty_completeness(): void |
| class ReverseStageTest extends TestCase | ||
| { | ||
| public function test_impact_can_never_be_seeded(): void |
| class WorkAdapterTest extends TestCase | ||
| { |
| class ReverseStageTest extends TestCase | ||
| { |
| 'acceptanceCriteria' => $values['acceptanceCriteria'] ?? '', | ||
| 'outcomeImpact' => $values['outcomeImpact'] ?? '', | ||
| 'editFrom' => $values['editFrom'] ?? '', |
| // Filter to open tickets only — DONE work doesn't need capacity. | ||
| $openTickets = array_values(array_filter($tickets, fn ($t) => ! $this->isDone($t))); | ||
|
|
| $ts = strtotime($rawDate); | ||
| if ($ts === false) { | ||
| $errors[] = "line {$lineNo}: unparseable dateRecorded '{$rawDate}'"; | ||
|
|
||
| continue; | ||
| } | ||
|
|
||
| $rows[] = [ | ||
| 'itemId' => $itemId, | ||
| 'value' => (float) $rawValue, | ||
| 'userId' => ($idxUser !== false && isset($record[$idxUser]) && $record[$idxUser] !== '') ? (int) $record[$idxUser] : null, | ||
| 'dateRecorded' => date('Y-m-d H:i:s', $ts), | ||
| ]; |
| /** | ||
| * Shared, period-aware reporting engine feeding the project, plan and strategy report screens. | ||
| * | ||
| * All methods operate on explicit project-id sets so higher levels (plan = child projects, | ||
| * strategy = descendant projects, later company = all projects) compose the same building | ||
| * blocks. Ids are filtered to projects the requesting user may view before any query runs, | ||
| * and the repository re-applies the SQL access predicate — defense in depth. | ||
| */ |
| // Filter to open tickets only — DONE work doesn't need capacity. | ||
| $openTickets = array_values(array_filter($tickets, fn ($t) => ! $this->isDone($t))); | ||
|
|
| private function isDone(array $t): bool | ||
| { | ||
| // Numeric statuses vary per project; treat -1 and 0 as closed/archived by convention. | ||
| $status = (int) ($t['status'] ?? 0); | ||
|
|
||
| return $status <= 0; | ||
| } |
| @@ -0,0 +1,334 @@ | |||
| <?php | |||
|
|
|||
| namespace Tests\Unit\App\Domain\Reports\Services; | |||
| @@ -0,0 +1,175 @@ | |||
| <?php | |||
|
|
|||
| namespace Tests\Unit\App\Domain\Reports\Models; | |||
| public string $appVersion = '3.9.8'; | ||
|
|
||
| public string $dbVersion = '3.5.20'; | ||
| public string $dbVersion = '3.5.22'; |
|
Maintainer triage (advisory — merge authority stays with @marcelfolaron / @broskees) · first pass · status: ready-for-review (blocked by merge-order) · priority: P2 · next action: hold for plugin #74 + verify submodule pointer · owner: @marcelfolaron / @broskees 1. Intent: Core companion to plugins #74 — bumps the 2. Change requests:
3. Acceptance checklist (before merge):
4. CI status: Advisory review only — not an approval, and I am not marking this ready or merging. |
|
Maintainer triage — CI follow-up (advisory · merge authority stays with @marcelfolaron / @broskees) · status: blocked (CI + merge-order) · priority: P2 · next action: fix failing Read the check-runs on head
Confirmed merge-order state: plugins #74 is still open / unmerged as of this pass. Per CR #1, this pointer-bump PR must not merge until #74 lands on
Recommend @gloriafolaron pull the 2 annotations from the Advisory review only — not an approval, and I am not marking this ready or merging. |
Rebuilds #3651 cleanly on current master. Its plugin-side reverse-engineer code (WorkAdapter, ReverseStage, guide-mode authoring) is now merged to plugin main (was plugins #74/#75), and the reports lineage it carried is superseded by #3643 (already on master) — so all that remains as core is this test coverage plus the submodule pointer bump to plugin main. - WorkAdapterTest: 7 tests / 20 assertions (completeness, metric-target classification, hidden-goal counting, sparse levels). - ReverseStageTest: 9 tests / 44 assertions (confidence→selection, canvas order inputs→impact, guide prompts). Both green against plugin main. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GYSPNgLdUwEnxgYqTxMc85
2cce2b3 to
d8fcec3
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
tests/Unit/app/Plugins/StrategyPro/Services/WorkAdapterTest.php:39
- These plugin unit tests will fatal in OSS/CI checkouts where
app/Pluginsis empty (private submodule). Other plugin tests in this repo guard against missing plugin classes by skipping (seetests/Unit/app/Plugins/PgmPro/Domain/Resources/Services/ResourceStructureServiceTest.php:42-49). Add the same skip here so the core repo test suite remains runnable without private plugins.
class WorkAdapterTest extends TestCase
{
public function test_empty_strategy_returns_empty_completeness(): void
tests/Unit/app/Plugins/StrategyPro/Services/ReverseStageTest.php:23
- This test will fatal when the StrategyPro plugin isn't available (core OSS CI often checks out without the private
app/Pluginssubmodule). Add a guard to skip whenReverseStagecan't be autoloaded, consistent with the existing plugin test pattern intests/Unit/app/Plugins/PgmPro/....
class ReverseStageTest extends TestCase
{
public function test_impact_can_never_be_seeded(): void
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
tests/Unit/app/Plugins/StrategyPro/Services/WorkAdapterTest.php:39
- These unit tests will fatal in OSS/CI checkouts where
app/Pluginsis empty (private submodule). Other plugin unit tests (e.g.tests/Unit/app/Plugins/PgmPro/...) guard this by skipping when the plugin class is not present. Add asetUp()thatmarkTestSkipped()whenWorkAdapteris unavailable so core CI remains green when submodules aren't checked out.
class WorkAdapterTest extends TestCase
{
public function test_empty_strategy_returns_empty_completeness(): void
tests/Unit/app/Plugins/StrategyPro/Services/ReverseStageTest.php:23
- Like other plugin unit tests, this file should skip cleanly when the private
app/Pluginssubmodule isn't present; otherwiseuse Leantime\Plugins\StrategyPro\Services\ReverseStage;triggers a fatal before PHPUnit can report a skipped test. Add asetUp()guard thatmarkTestSkipped()whenReverseStagecan't be autoloaded.
class ReverseStageTest extends TestCase
{
public function test_impact_can_never_be_seeded(): void
|
Closing per Marcel: this PR's only content was two StrategyPro unit tests ( The tests now live where they can actually run: plugins PR Leantime/plugins#78 ( |
Summary
Core companion to Leantime/plugins#74 — bumps the StrategyPro submodule pointer to the plugin's
feat/logic-model-reverseHEAD and lands unit tests for the reverse flow's read half.What's in the PR
app/Plugins.tests/Unit/app/Plugins/StrategyPro/Services/WorkAdapterTest.php— 7 tests, 20 assertions covering the four highest-regression-risk behaviors onWorkAdapter:endValue=0is treated as no-target (avoids "0% of 0" nonsense).The plugin PR carries the actual services, HxControllers, and templates. See there for the full spec + verification checklist.
Merge order
plugins:main.Test plan
./vendor/bin/phpunit tests/Unit/app/Plugins/StrategyPro/Services/WorkAdapterTest.php— 7 tests pass.🤖 Generated with Claude Code