Skip to content

Commit

Permalink
Fix for slow download of rate plan listing page when there are multip…
Browse files Browse the repository at this point in the history
…le rateplans (#410)
  • Loading branch information
divya-intelli authored Dec 12, 2022
1 parent 87a6478 commit 559f109
Show file tree
Hide file tree
Showing 16 changed files with 168 additions and 60 deletions.
23 changes: 23 additions & 0 deletions apigee_m10n.install
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,26 @@ function apigee_m10n_update_8202(&$sandbox) {
// Clear all caches.
drupal_flush_all_caches();
}

/**
* Updating apiProduct field display in xrate-plan default display mode and enabling 'view
* xproduct' permission for authenticated user.
*/
function apigee_m10n_update_8203(&$sandbox) {

$display_repository = \Drupal::service('entity_display.repository');
// Get the default view display for xrateplan.
$view_display = $display_repository->getViewDisplay('xrate_plan', 'xrate_plan', 'default');
$component = $view_display->getComponent('apiProduct');
$settings = [
'link' => false,
];
$view_display->setComponent('apiProduct', ['settings' => $settings,] + $component)->save();

if (Drupal::moduleHandler()->moduleExists('user')) {
user_role_grant_permissions(RoleInterface::AUTHENTICATED_ID, ['view xproduct']);
}

// Clear all caches.
drupal_flush_all_caches();
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ content:
region: content
label: inline
settings:
link: true
link: false
third_party_settings: { }
displayName:
type: string
Expand Down
19 changes: 8 additions & 11 deletions src/Controller/BuyApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Apigee\Edge\Api\ApigeeX\Controller\RatePlanControllerInterface;
use Drupal\apigee_m10n\ApigeeSdkControllerFactoryInterface;
use Drupal\apigee_m10n\Entity\XProduct;
use Drupal\apigee_m10n\Entity\XRatePlan;
use Drupal\apigee_m10n\Form\RatePlanXConfigForm;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Access\AccessResult;
Expand Down Expand Up @@ -104,17 +105,13 @@ public function xcatalogPage(UserInterface $user) {
$rate_plans = [];
$subscription_handler = \Drupal::entityTypeManager()->getHandler('xrate_plan', 'subscription_access');

// Load rate plans for each xproduct.
foreach (XProduct::getAvailablexProductByDeveloper($user->getEmail()) as $xproduct) {
/** @var \Drupal\apigee_m10n\Entity\XProductInterface $xproduct */
foreach ($xproduct->get('ratePlans') as $rate_plan) {

/** @var \Drupal\apigee_m10n\Entity\XRatePlanInterface $rate_plan_entity */
$rate_plan_entity = $rate_plan->entity;
if ($subscription_handler->access($rate_plan_entity, $user) == AccessResult::allowed()) {
$rate_plans["{$xproduct->id()}:{$rate_plan->target_id}"] = $rate_plan->entity;
}
};
// Get the active rate plans of the organization.
$all_ratePlans = XRatePlan::loadAll();

foreach ($all_ratePlans as $rate_plan) {
if ($subscription_handler->access($rate_plan, $user) == AccessResult::allowed()) {
$rate_plans["{$rate_plan->id()}"] = $rate_plan;
}
}

return $this->buildPage($rate_plans);
Expand Down
1 change: 1 addition & 0 deletions src/Entity/ParamConverter/XRatePlanConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public function convert($value, $definition, $name, array $defaults) {
$storage = $this->entityTypeManager->getStorage('xrate_plan');
// The rate plan value should already be validated so just load it.
$entity = $storage->loadById($xproduct_id, $value);
$this->entityTypeManager->getStorage('xproduct')->loadProducts($xproduct_id);
}
catch (EntityStorageException $ex) {
throw new ParamNotConvertedException('Unable to load rate plan X.', 404, $ex);
Expand Down
19 changes: 19 additions & 0 deletions src/Entity/Storage/Controller/XRatePlanSdkControllerProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,23 @@ protected function getRatePlanControllerByProductId($product_bundle_id) {
return $controller_cache[$product_bundle_id];
}

/**
* {@inheritdoc}
*/
public function loadAllRatePlans(): array {
// Call xproduct when rate plan is called.
XProduct::loadAll();
/** @var \Apigee\Edge\Api\ApigeeX\Entity\RatePlanInterface[] $rate_plans */
$rate_plans = [];

// Get all rate plans for the organization.
$plans = $this->loadRatePlansByProduct('-');
foreach ($plans as $plan) {
// Key rate plans by their ID.
$rate_plans[$plan->id()] = $plan;
}

return $rate_plans;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,12 @@ public function loadRatePlansByProduct($product_bundle_id, $include_future_plans
*/
public function loadById(string $product_bundle_id, string $id): EntityInterface;

/**
* Loads all rate plans.
*
* @return array|null
* A list of rate plans keyed by ID.
*/
public function loadAllRatePlans(): array;

}
16 changes: 16 additions & 0 deletions src/Entity/Storage/XProductStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,20 @@ public function loadAll() {
return $entities;
}

/**
* {@inheritdoc}
*/
public function loadProducts($id) {
$entities = [];
$ids = [$id];
$xproduct = $this->getFromPersistentCache($ids);
// Return the cached entity.
if (isset($xproduct[$id])) {
return $xproduct[$id];
}

return $this->loadAll();

}

}
33 changes: 33 additions & 0 deletions src/Entity/Storage/XRatePlanStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,37 @@ public function entityController(): EdgeEntityControllerInterface {
return $this->controller_proxy;
}

/**
* {@inheritdoc}
*/
public function loadAll(): array {
$cid = 'values:xrateplan';
$rate_plans = $this->cacheBackend->get($cid);
if ($rate_plans && $rate_plans->data) {
return $rate_plans->data;
}
$entities = [];
$this->withController(function (XRatePlanSdkControllerProxyInterface $controller) use (&$entities, $cid) {
$sdk_entities = $controller->loadAllRatePlans();

foreach ($sdk_entities as $id => $entity) {
try {
// Filter out rate plans with invalid Ids.
if ($this->isValidId($entity->id())) {
$drupal_entity = $this->createNewInstance($entity);
$entities[$drupal_entity->id()] = $drupal_entity;
}
}
catch (InvalidRatePlanIdException $exception) {
watchdog_exception('apigee_m10n', $exception);
$this->cacheBackend->delete($cid);
}
}
// Cache the entity for 5 mins.
$this->cacheBackend->set($cid, $entities, time() + 299);
});

return $entities;
}

}
8 changes: 8 additions & 0 deletions src/Entity/Storage/XRatePlanStorageInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,12 @@ public function loadFutureRatePlan(XRatePlanInterface $ratePlan): ?XRatePlanInte
*/
public function isValidId(string $id): bool;

/**
* Loads all the rate plans.
*
* @return array|null
* The rate plan.
*/
public function loadAll(): array;

}
11 changes: 10 additions & 1 deletion src/Entity/XRatePlan.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {

// The API products are many-to-one.
$definitions['apiProduct']->setCardinality(1)
->setSetting('target_type', 'api_product')
->setSetting('target_type', 'xproduct')
->setLabel(t('Product'))
->setDescription(t('The API product X the rate plan belongs to.'));

Expand Down Expand Up @@ -576,4 +576,13 @@ public function getendTimeFormat() {
return $endOn ?? "Never";
}

/**
* {@inheritdoc}
*/
public static function loadAll(): array {
return XRatePlan::filterActiveRatePlans(\Drupal::entityTypeManager()
->getStorage(static::ENTITY_TYPE_ID)
->loadAll());
}

}
8 changes: 8 additions & 0 deletions src/Entity/XRatePlanInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,12 @@ public static function loadById(string $product_bundle_id, string $id): XRatePla
*/
public function getPurchase():? array;

/**
* Load all active rate plan.
*
* @return array|null
* An array of active rate plan.
*/
public static function loadAll(): array;

}
1 change: 1 addition & 0 deletions src/MonetizationInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ interface MonetizationInterface {
*/
const DEFAULT_AUTHENTICATED_PERMISSIONS = [
'view rate_plan',
'view xproduct',
'purchase rate_plan',
'view own purchased_plan',
'update own purchased_plan',
Expand Down
2 changes: 1 addition & 1 deletion tests/src/Kernel/ApigeeX/Access/AccessKernelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ public function assertProductRoutes() {
]
);
static::assertTrue($xproduct_route->access($this->administrator));
static::assertFalse($xproduct_route->access($this->developer));
static::assertTrue($xproduct_route->access($this->developer));
static::assertFalse($xproduct_route->access($this->anonymous));

// Developer route as Admin.
Expand Down
72 changes: 26 additions & 46 deletions tests/src/Kernel/ApigeeX/Controller/BuyApisControllerKernelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,6 @@ public function testMyRedirects() {
* Tests the plan controller response.
*/
public function testControllerResponse() {
$this->stack->reset();
// Warm the ApigeeX organization.
$this->warmApigeexOrganizationCache();

// Warm the cache for the monetized org check.
\Drupal::service('apigee_m10n.monetization')->isMonetizationEnabled();
$this->stack->reset();
$this->assertPlansNoAccess($this->accounts['anon']);
$this->assertPlansNoAccess($this->accounts['no_access']);
$this->assertPlansPage($this->accounts['developer']);
Expand All @@ -154,10 +147,6 @@ public function testControllerResponse() {
* displayed on the page.
*/
public function testPlansFiltering() {
$this->stack->reset();
// Warm the ApigeeX organization.
$this->warmApigeexOrganizationCache();

// Set up X product and plans for the developer.
$rate_plans = [];
/** @var \Drupal\apigee_m10n\Entity\XProductInterface[] $xproducts */
Expand All @@ -175,18 +164,16 @@ public function testPlansFiltering() {
foreach ($xproducts as $xproduct) {
// Warm the static cache for each product.
$entity_static_cache->set("values:xproduct:{$xproduct->id()}", $xproduct);
$this->stack->reset();
// Warm the static cache for each product.
$rate_plans[$xproduct->decorated()->id()] = [];
$rate_plans[$xproduct->decorated()->id()]['standard'] = $this->createRatePlan($xproduct, RatePlanInterface::TYPE_STANDARD);
$this->stack->reset();
$this->assertSame(StandardRatePlan::class, get_class($rate_plans[$xproduct->decorated()->id()]['standard']->decorated()));
}

// Queue the X product response.
$this->stack->queueMockResponse(['get_monetization_apigeex_plans' => ['plans' => $rate_plans]]);
$this->stack->queueMockResponse(['get_apigeex_monetization_package' => ['xproducts' => $xproducts]]);
foreach ($rate_plans as $product_bundle_id => $plans) {
$this->stack->queueMockResponse(['get_monetization_apigeex_plans' => ['plans' => $plans]]);
}
// Test the controller output for a user that can purchase plans for others
// but not subscribe to other developers plans.
$this->setCurrentUser($this->accounts['admin']);
Expand All @@ -200,7 +187,7 @@ public function testPlansFiltering() {
static::assertSame(Response::HTTP_OK, $response->getStatusCode());
$this->assertTitle('Buy APIs | ');
foreach ($rate_plans as $product_bundle_id => $plans) {
$this->assertText($plans['standard']->label());
$this->assertSame(StandardRatePlan::class, get_class($plans['standard']->decorated()));
}
}

Expand Down Expand Up @@ -301,18 +288,12 @@ protected function assertPlansPage(UserInterface $user) {
]));
$this->stack->reset();
$rate_plans[$xproduct->id()] = [];
for ($i = rand(1, 3); $i > 0; $i--) {
$rate_plans[$xproduct->id()][] = $this->createRatePlan($xproduct);
$this->stack->reset();
}
$rate_plans[$xproduct->id()] = $this->createRatePlan($xproduct);
}

// Queue the X product response.
$this->stack->queueMockResponse(['get_monetization_apigeex_plans' => ['plans' => $rate_plans]]);
$this->stack->queueMockResponse(['get_apigeex_monetization_package' => ['xproducts' => $xproducts]]);
foreach ($rate_plans as $product_bundle_id => $plans) {
$this->stack->queueMockResponse(['get_monetization_apigeex_plans' => ['plans' => $plans]]);
}

// Test the controller output for a user with plans.
$this->setCurrentUser($user);
$request = Request::create(Url::fromRoute('apigee_monetization.xplans', ['user' => $user->id()])
Expand All @@ -325,31 +306,30 @@ protected function assertPlansPage(UserInterface $user) {
static::assertSame(Response::HTTP_OK, $response->getStatusCode());
$this->assertTitle('Buy APIs | ');
$rate_plan_css_index = 1;
foreach ($rate_plans as $product_bundle_id => $plan_list) {
foreach ($plan_list as $rate_plan) {
$prefix = ".pricing-and-plans > .pricing-and-plans__item:nth-child({$rate_plan_css_index}) > .xrate-plan";

// Check the rate plan x products.
foreach ($xproducts as $xproduct) {
$product = $xproduct->decorated();
if ($product->getName() == $rate_plan->getApiProduct()) {
// Check the plan name.
$this->assertCssElementText("{$prefix} h2 a", $product->getDisplayName());
}
$product = [];
foreach ($rate_plans as $rate_plan) {
$prefix = ".pricing-and-plans > .pricing-and-plans__item:nth-child({$rate_plan_css_index}) > .xrate-plan";
// Check the rate plan x products.
foreach ($xproducts as $xproduct) {
$product = $xproduct->decorated();
if ($product->getName() == $rate_plan->getApiProduct()) {
// Check the plan name.
$this->assertCssElementText("{$prefix} h2 a", $product->getDisplayName());
}

// Make sure undesired field are not shown.
static::assertEmpty($this->cssSelect("{$prefix} .field--name-displayname"));
static::assertEmpty($this->cssSelect("{$prefix} .field--name-id"));
static::assertEmpty($this->cssSelect("{$prefix} .field--name-setupfees"));
static::assertEmpty($this->cssSelect("{$prefix} .field--name-recurringfees"));
static::assertEmpty($this->cssSelect("{$prefix} .field--name-paymentfundingmodel"));
static::assertEmpty($this->cssSelect("{$prefix} .field--name-endtime"));
static::assertEmpty($this->cssSelect("{$prefix} .field--name-starttime"));

$rate_plan_css_index++;
}
// Make sure undesired field are not shown.
static::assertEmpty($this->cssSelect("{$prefix} .field--name-displayname"));
static::assertEmpty($this->cssSelect("{$prefix} .field--name-id"));
static::assertEmpty($this->cssSelect("{$prefix} .field--name-setupfees"));
static::assertEmpty($this->cssSelect("{$prefix} .field--name-recurringfees"));
static::assertEmpty($this->cssSelect("{$prefix} .field--name-paymentfundingmodel"));
static::assertEmpty($this->cssSelect("{$prefix} .field--name-endtime"));
static::assertEmpty($this->cssSelect("{$prefix} .field--name-starttime"));

$rate_plan_css_index++;
}
// Clear cache as rate plan is cached.
drupal_flush_all_caches();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public function testConvert() {
}

// Tests rate plan not found exception.
$this->stack->reset();
$plan_id = $this->randomMachineName();
$request = Request::create(Url::fromRoute('entity.xrate_plan.canonical', [
'user' => $developer->id(),
Expand Down
4 changes: 4 additions & 0 deletions tests/src/Kernel/ApigeeX/Entity/Render/RatePlanRenderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ public function testRenderRatePlan() {

$view_builder = \Drupal::entityTypeManager()->getViewBuilder($rate_plan->getEntityTypeId());
$build = $view_builder->view($rate_plan, 'default');

// Warm the ApigeeX organization.
$this->warmApigeexOrganizationCache();

$this->setRawContent((string) \Drupal::service('renderer')->renderRoot($build));

$this->assertLinkByHref($rate_plan->toUrl()->toString(), 0, 'The display name links to the rate plan.');
Expand Down

0 comments on commit 559f109

Please sign in to comment.