From 7e7fabccfc97de38bf03058e585ab93f6f93adc3 Mon Sep 17 00:00:00 2001 From: Jomar Santos Date: Wed, 16 Oct 2024 15:56:52 -0700 Subject: [PATCH] feat: [MINT-2723] integration onboarding steps (#146) * feat: [MINT-2723] integration onboarding steps * [MINT-2723] update tests * auto-changelog --------- Co-authored-by: magento-bot --- .../Integration/Edit/Tab/HowToActivate.php | 70 ++++++++++++++- .../Edit/Tab/HowToActivateTest.php | 85 +++++++++++++++---- .../web/js/config/activation-status.js | 6 +- 3 files changed, 143 insertions(+), 18 deletions(-) diff --git a/Block/Adminhtml/Integration/Edit/Tab/HowToActivate.php b/Block/Adminhtml/Integration/Edit/Tab/HowToActivate.php index ba7e6ea..685fe90 100644 --- a/Block/Adminhtml/Integration/Edit/Tab/HowToActivate.php +++ b/Block/Adminhtml/Integration/Edit/Tab/HowToActivate.php @@ -28,10 +28,17 @@ class HowToActivate extends Field * @var string */ protected $_template = 'Extend_Integration::system/config/how-to-activate.phtml'; + // TODO: MINT-2855 Switch to the following template instead of the one above + // protected $_template = 'Extend_Integration::system/config/integration-status.phtml'; private Environment $environment; private IntegrationServiceInterface $integrationService; private Context $context; private AccessTokenBuilder $accessTokenBuilder; + private $stepMap = [ + 0 => 'activation_required', + 1 => 'identity_link_required', + 2 => 'complete' + ]; /** * Intro constructor @@ -75,14 +82,40 @@ protected function _getElementHtml(AbstractElement $element): string */ public function getIntegrations() { + $urlBuilder = $this->context->getUrlBuilder(); $integrationsStatuses = []; $environmentOptions = $this->environment->toOptionArray(); if ($environmentOptions) { foreach ($environmentOptions as $environmentOption) { $integration = $this->integrationService->get($environmentOption['value']); + + $identityLinkUrl = $integration->getIdentityLinkUrl() . '?oauth_consumer_key=' . $integration->getConsumerKey() . '&success_call_back=' . $urlBuilder->getCurrentUrl();; + $isAuthHandshakeComplete = $this->isAuthHandshakeComplete($integration); + $isIdentityLinkConfirmed = $this->isIdentityLinkConfirmed($integration); + $isIntegrationComplete = $isAuthHandshakeComplete && $isIdentityLinkConfirmed; + + $integrationCreatedAt = $integration->getCreatedAt(); + $integrationUpdatedAt = $integration->getUpdatedAt(); + + $oauthActivatedAt = $isAuthHandshakeComplete && $integrationUpdatedAt > $integrationCreatedAt ? $integrationUpdatedAt : null; + + $prevActivationFailed = !$isAuthHandshakeComplete && $integrationUpdatedAt > $integrationCreatedAt; + + $currentStep = $this->stepMap[0]; + if ($isIntegrationComplete) { + $currentStep = $this->stepMap[2]; + } elseif ($isAuthHandshakeComplete) { + $currentStep = $this->stepMap[1]; + } + $integrationsStatuses[] = [ + 'activation_status' => $this->getStatus($integration), // TODO: MINT-2855 Remove the activation status as this was only used by old template + 'current_step' => $currentStep, + 'identity_link_url' => $identityLinkUrl, 'integration_id' => $integration->getId(), - 'activation_status' => $this->getStatus($integration) + 'integration_name' => $environmentOption['label'], + 'oauth_activated_at' => $oauthActivatedAt, + 'prev_activation_failed' => $prevActivationFailed ]; } } @@ -114,4 +147,39 @@ private function getStatus($integration) } } + + /** + * Determines whether the integration has successfully performed the oauth handshake with Extend. + * This is performed after the merchant clicks on Activate on the Magento Integrations page for + * the Extend integration. + * + * @param \Magento\Integration\Model\Integration $integration + * @return bool + */ + private function isAuthHandshakeComplete($integration): bool + { + $integrationStatus = $integration->getStatus(); + + return $integrationStatus === 1; + } + + /** + * Determines whether the merchant has completed the identity link handshake with Extend. + * This is performed when the identity link pops up to the Merchant Portal and the merchant + * successfully connects their Extend account to their Magento instance. + * + * @param \Magento\Integration\Model\Integration $integration + * @return bool + */ + private function isIdentityLinkConfirmed($integration): bool + { + $clientData = $this->accessTokenBuilder->getExtendOAuthClientData($integration->getId()); + + if (isset($clientData['clientId']) && isset($clientData['clientSecret']) + ) { + return true; + } else { + return false; + } + } } diff --git a/Test/Unit/Block/Adminhtml/Integration/Edit/Tab/HowToActivateTest.php b/Test/Unit/Block/Adminhtml/Integration/Edit/Tab/HowToActivateTest.php index e88774a..465b32c 100644 --- a/Test/Unit/Block/Adminhtml/Integration/Edit/Tab/HowToActivateTest.php +++ b/Test/Unit/Block/Adminhtml/Integration/Edit/Tab/HowToActivateTest.php @@ -19,6 +19,11 @@ class HowToActivateTest extends TestCase */ private HowToActivate $howToActivate; + /** + * @var \Magento\Framework\Url&\PHPUnit\Framework\MockObject\Stub + */ + private $urlBuilder; + /** * @var \Magento\Backend\Block\Template\Context|\PHPUnit\Framework\MockObject\Stub */ @@ -63,9 +68,24 @@ class HowToActivateTest extends TestCase */ private $accessTokenBuilder; + private $callbackUrl = 'https://magento-instance.com/admin'; + + private $integration1OauthClientData = [ + 'clientId' => '89rjh89tyrhug3897y', + 'clientSecret' => 'fbhn39ry34rhfsdfi98', + ]; + + private $integration2OauthClientData = [ + 'clientId' => null, + 'clientSecret' => null, + ]; + public function setUp(): void { + $this->urlBuilder = $this->createMock(\Magento\Framework\Url::class); + $this->urlBuilder->method('getCurrentUrl')->willReturn($this->callbackUrl); $this->context = $this->createStub(\Magento\Backend\Block\Template\Context::class); + $this->context->method('getUrlBuilder')->willReturn($this->urlBuilder); $this->environment = $this->createMock(Environment::class); $this->integrationService = $this->createMock(IntegrationServiceInterface::class); $this->accessTokenBuilder = $this->createMock(AccessTokenBuilder::class); @@ -83,20 +103,50 @@ public function setUp(): void ['value' => 1, 'label' => 'Extend Integration - Prod'], ['value' => 2, 'label' => 'Extend Integration - Demo'], ]; - - $this->integrationModel1 = $this->createConfiguredMock(\Magento\Integration\Model\Integration::class, [ - 'getId' => 1, - 'getStatus' => 1 - ]); - $this->integrationModel2 = $this->createConfiguredMock(\Magento\Integration\Model\Integration::class, [ - 'getId' => 2, - 'getStatus' => 0 - ]); + $this->integrationModel1 = $this->getMockBuilder(\Magento\Integration\Model\Integration::class) + ->addMethods( + ['getIdentityLinkUrl', 'getConsumerKey'] + ) + ->onlyMethods(['getId', 'getStatus']) + ->disableOriginalConstructor() + ->getMock(); + $this->integrationModel1->method('getId')->willReturn(1); + $this->integrationModel1->method('getStatus')->willReturn(1); + $this->integrationModel1->method('getIdentityLinkUrl')->willReturn('https://merchants.extend.com/magento'); + $this->integrationModel1->method('getConsumerKey')->willReturn('prodConsumerKey'); + + $this->integrationModel2 = $this->getMockBuilder(\Magento\Integration\Model\Integration::class) + ->addMethods( + ['getIdentityLinkUrl', 'getConsumerKey'] + ) + ->onlyMethods(['getId', 'getStatus']) + ->disableOriginalConstructor() + ->getMock(); + $this->integrationModel2->method('getId')->willReturn(2); + $this->integrationModel2->method('getStatus')->willReturn(0); + $this->integrationModel2->method('getIdentityLinkUrl')->willReturn('https://merchants.demo.extend.com/magento'); + $this->integrationModel2->method('getConsumerKey')->willReturn('demoConsumerKey'); $this->activationStatusData = [ - ['integration_id' => 1, 'activation_status' => 1], - ['integration_id' => 2, 'activation_status' => 0] + [ + 'integration_id' => 1, + 'activation_status' => 1, + 'current_step' => 'complete', + 'identity_link_url' => 'https://merchants.extend.com/magento?oauth_consumer_key=prodConsumerKey&success_call_back='.$this->callbackUrl, + 'integration_name' => 'Extend Integration - Prod', + 'oauth_activated_at' => null, + 'prev_activation_failed' => false, + ], + [ + 'integration_id' => 2, + 'activation_status' => 0, + 'current_step' => 'activation_required', + 'identity_link_url' => 'https://merchants.demo.extend.com/magento?oauth_consumer_key=demoConsumerKey&success_call_back='.$this->callbackUrl, + 'integration_name' => 'Extend Integration - Demo', + 'oauth_activated_at' => null, + 'prev_activation_failed' => false, + ], ]; } @@ -113,10 +163,15 @@ public function testGetIntegrations() $this->integrationService->expects($this->exactly(2))->method('get') ->willReturnOnConsecutiveCalls($this->integrationModel1, $this->integrationModel2); - $this->accessTokenBuilder->expects(($this->exactly(2))) - ->method('getExtendOAuthClientData') - ->willReturnOnConsecutiveCalls(['clientId' => '89rjh89tyrhug3897y', 'clientSecret' => 'fbhn39ry34rhfsdfi98'], ['clientId' => null, 'clientSecret' => null]); + $this->accessTokenBuilder->expects(($this->exactly(4))) + ->method('getExtendOAuthClientData') + ->willReturnOnConsecutiveCalls( + $this->integration1OauthClientData, + $this->integration1OauthClientData, + $this->integration2OauthClientData, + $this->integration2OauthClientData, + ); $this->assertEquals($this->howToActivate->getIntegrations(), $this->activationStatusData); } -} \ No newline at end of file +} diff --git a/view/adminhtml/web/js/config/activation-status.js b/view/adminhtml/web/js/config/activation-status.js index d281ccf..1a5710e 100644 --- a/view/adminhtml/web/js/config/activation-status.js +++ b/view/adminhtml/web/js/config/activation-status.js @@ -45,8 +45,10 @@ define([], function () { if (found) return new Date(found) // If there is no localStorage, we can leverage the the date of when the integration was activated - if (integration.oauthActivatedAt) - return new Date(integration.oauthActivatedAt) + // Since this is set by the server, it will be coming through as UTC + if (integration.oauthActivatedAt) { + return new Date(`${integration.oauthActivatedAt}Z`) + } return null }