Skip to content

Add the product-review-approved earning trigger + channel resolver#30

Closed
loevgaard wants to merge 2 commits into
pr/29-action-trigger-registrationfrom
pr/30-action-trigger-review
Closed

Add the product-review-approved earning trigger + channel resolver#30
loevgaard wants to merge 2 commits into
pr/29-action-trigger-registrationfrom
pr/30-action-trigger-review

Conversation

@loevgaard

Copy link
Copy Markdown
Member

The second action trigger — a product-review-approved bonus — plus the channel resolver that context-less triggers need. Stacked on #29 (reuses ActionPointsAwarder).

What it adds

  • TriggerChannelResolverInterface (+ DefaultTriggerChannelResolver) — resolves which channels a trigger without a shop-channel context awards on: the customer's existing loyalty-account channels (your call). An extension point; LoyaltyAccountRepository gained findByCustomer.
  • ReviewApprovedStateMachineListener — awards the product_review_approved rules when a review is accepted, on each resolved channel. Approval is an admin transition (no shop channel), so it uses the resolver. Bridges both state-machine adapters like the order-award listener (workflow tag + winzou callback via the extension prepend); idempotency + a per-(review, channel) source identifier cover overlap.

Tests

  • Functional: DefaultTriggerChannelResolverTest (resolves account channels; empty for no account).
  • Unit: ReviewApprovedStateMachineListenerTest (both adapters + non-review/non-customer guards); the extension test now asserts the review winzou callback.
  • No new dependencies (ProductReviewInterface is Core). Green locally: PHPStan max, ECS, Rector, unit 126 + functional 55, lint:container, normalize/validate.

Base is #29; review/merge that first. Once #29 lands I'll rebase this onto master.

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.40%. Comparing base (a2dc56b) to head (e1c1a2c).

Additional details and impacted files
@@                           Coverage Diff                           @@
##             pr/29-action-trigger-registration      #30      +/-   ##
=======================================================================
- Coverage                                94.53%   94.40%   -0.13%     
- Complexity                                 443      447       +4     
=======================================================================
  Files                                       70       69       -1     
  Lines                                     1280     1270      -10     
=======================================================================
- Hits                                      1210     1199      -11     
- Misses                                      70       71       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

],
],
],
ProductReviewTransitions::GRAPH => [

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add listener on Symfony workflow also

@loevgaard loevgaard force-pushed the pr/30-action-trigger-review branch from 33d13ff to 754912b Compare July 10, 2026 13:04
The second action trigger, plus the channel-resolution the context-less triggers need.

- TriggerChannelResolverInterface (+ DefaultTriggerChannelResolver) resolves the channels a
  context-less trigger awards on: the customer's existing loyalty-account channels. It's an extension
  point, and LoyaltyAccountRepository gained findByCustomer for it.
- ReviewApprovedStateMachineListener awards the `product_review_approved` rules when a product review
  is accepted, on each resolved channel. Approval happens in admin (no shop channel), so the resolver
  is used instead of the current channel. Bridges both state-machine adapters like the order-award
  listener (workflow tag + winzou callback via the extension prepend); the ledger's idempotency covers
  overlap and the per-(review,channel) source identifier.

Tests: DefaultTriggerChannelResolverTest (functional) + ReviewApprovedStateMachineListenerTest (unit,
both adapters + guards); the extension test now asserts the review winzou callback. Unit 126 +
functional 55 green. No new dependencies (ProductReviewInterface is Core).
@loevgaard loevgaard force-pushed the pr/30-action-trigger-review branch from 754912b to e1c1a2c Compare July 10, 2026 18:12
<service id="Setono\SyliusLoyaltyPlugin\Earning\Trigger\ReviewApprovedStateMachineListener" public="true">
<argument type="service" id="Setono\SyliusLoyaltyPlugin\Earning\ActionPointsAwarderInterface" />
<argument type="service" id="Setono\SyliusLoyaltyPlugin\Earning\TriggerChannelResolverInterface" />
<tag name="kernel.event_listener" event="workflow.sylius_product_review.completed.accept" method="onWorkflowReviewAccepted" />

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this event be added as an event subscriber instead?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It intentionally matches the order-award and redemption state-machine listeners: an event_listener tag for the workflow transition (workflow.sylius_product_review.completed.accept) plus a winzou callback registered in the extension prepend. Making only this one a subscriber would diverge from those two. If you'd prefer subscribers for all the state-machine bridges, I'm happy to convert the three together as a separate cleanup — let me know.

/**
* @return iterable<ChannelInterface>
*/
public function resolve(CustomerInterface $customer): iterable;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we miss more implementations than the DefaultTriggerChannelResolver. It should probably be a composite service? See https://github.com/Setono/composite-compiler-pass

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — TriggerChannelResolverInterface is now a composite via setono/composite-compiler-pass. CompositeTriggerChannelResolver merges every service tagged setono_sylius_loyalty.trigger_channel_resolver (de-duplicated by channel code); DefaultTriggerChannelResolver is one tagged contributor, the interface aliases the composite, and the interface is registered for autoconfiguration so a project's own resolver is tagged automatically.

@loevgaard loevgaard deleted the branch pr/29-action-trigger-registration July 10, 2026 18:15
@loevgaard loevgaard closed this Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant