diff --git a/.circleci/RoboFile.php b/.circleci/RoboFile.php index 68ccf731..fbacafae 100644 --- a/.circleci/RoboFile.php +++ b/.circleci/RoboFile.php @@ -411,6 +411,9 @@ public function configureModuleDependencies() unset($config->require->{"drupal/core"}); $config->require->{"drupal/core-recommended"} = "~8.8"; + // Add rules for testing apigee_edge_actions. + $config->require->{"drupal/rules"} = "^3.0@alpha"; + // We require Drupal console and drush for some tests. $config->require->{"drupal/console"} = "~1.0"; $config->require->{"drush/drush"} = "^9.7"; diff --git a/modules/apigee_edge_actions/README.md b/modules/apigee_edge_actions/README.md new file mode 100644 index 00000000..8098c048 --- /dev/null +++ b/modules/apigee_edge_actions/README.md @@ -0,0 +1,53 @@ +# Apigee Edge Actions + +The Apigee Edge Actions module provides rules integration for Apigee Edge. It makes it easy to automate tasks and react on events such as: + + * Sending an email when an App is created. + * Notify a developer when added to a Team. + * Notify admin when an API product is added to an App. + +## Events + +The following events are supported out of the box: + +### App +`\Drupal\apigee_edge\Entity\DeveloperApp` + +| Event | Name | +|---|---| +| After saving a new App | `apigee_edge_actions_entity_insert:developer_app` | +| After deleting an App | `apigee_edge_actions_entity_delete:developer_app` | +| After updating an App | `apigee_edge_actions_entity_insert:developer_app` | +| After adding an API Product | `apigee_edge_actions_entity_add_product:developer_app` | +| After removing an API Product | `apigee_edge_actions_entity_remove_product:developer_app` | + +### Team App +`\Drupal\apigee_edge_teams\Entity\TeamApp` + +| Event | Name | +|---|---| +| After saving a new Team App | `apigee_edge_actions_entity_insert:team_app` | +| After deleting an Team App | `apigee_edge_actions_entity_delete:team_app` | +| After updating an Team App | `apigee_edge_actions_entity_insert:team_app` | +| After adding an API Product | `apigee_edge_actions_entity_add_product:team_app` | +| After removing an API Product | `apigee_edge_actions_entity_remove_product:team_app` | + +### Team +`\Drupal\apigee_edge_teams\Entity\Team` + +| Event | Name | +|---|---| +| After saving a new Team | `apigee_edge_actions_entity_insert:team` | +| After deleting an Team | `apigee_edge_actions_entity_delete:team` | +| After updating an Team | `apigee_edge_actions_entity_insert:team` | +| After adding a team member | `apigee_edge_actions_entity_add_member:team` | +| After removing a team member | `apigee_edge_actions_entity_remove_member:team` | + +## Examples + +The `apigee_edge_actions_examples` module ships with some example rules you can use to test: + +1. Log a message when team is deleted. +2. Notify developer when added to a team +3. Notify developer when adding a new app +4. Notify site admins when app is created diff --git a/modules/apigee_edge_actions/apigee_edge_actions.info.yml b/modules/apigee_edge_actions/apigee_edge_actions.info.yml new file mode 100644 index 00000000..cdfc32b3 --- /dev/null +++ b/modules/apigee_edge_actions/apigee_edge_actions.info.yml @@ -0,0 +1,9 @@ +name: Apigee Edge Actions +description: Rules integration for Apigee Edge. +package: Apigee (Experimental) +type: module +core: 8.x +configure: entity.rules_reaction_rule.collection +dependencies: + - apigee_edge:apigee_edge + - rules:rules diff --git a/modules/apigee_edge_actions/apigee_edge_actions.module b/modules/apigee_edge_actions/apigee_edge_actions.module new file mode 100644 index 00000000..856ac3a7 --- /dev/null +++ b/modules/apigee_edge_actions/apigee_edge_actions.module @@ -0,0 +1,120 @@ +isFieldableEdgeEntityType($entity->getEntityType())) { + return; + } + + $dispatched_event_name = "apigee_edge_actions_entity_$event_name:{$entity->getEntityTypeId()}"; + + $arguments = [ + $entity->getEntityTypeId() => $entity + ]; + + // Note: Refactor this to plugins if more entity types requires custom + // arguments. + if ($entity instanceof AppInterface) { + if ($entity instanceof DeveloperAppInterface) { + // $entity->getCreatedBy() is deprecated, so to get the developer Drupal + // account we need to load the developer by UUID, then load the user by + // email. + // Note: $entity->getAppOwner() returns a developer UUID, which is + // different from a user's UUID, so we load the developer first and then + // the account. + $developer = Drupal::entityTypeManager() + ->getStorage('developer') + ->load($entity->getAppOwner()); + $user_id = $developer->getEmail(); + } + else { + /** @var \Drupal\apigee_edge_teams\Entity\TeamAppInterface $entity */ + // For TeamApps, getAppOwner() is a team name, not a developer or email, + // and we cannot rely on getCreatedBy() as it is deprecated, so we + // default to the current user for the developer. + $user_id = Drupal::currentUser()->getEmail(); + + // Add the team. + $team = Drupal::entityTypeManager() + ->getStorage('team') + ->load($entity->getAppOwner()); + $arguments['team'] = $team; + } + + // Add the developer. + $arguments['developer'] = user_load_by_mail($user_id); + } + + if ($event_name === 'update') { + $arguments["{$entity->getEntityTypeId()}_unchanged"] = $entity->original; + } + + /** @var \Drupal\apigee_edge\Entity\EdgeEntityInterface $entity */ + Drupal::service('event_dispatcher') + ->dispatch($dispatched_event_name, new EdgeEntityEventEdge($entity, $arguments)); +} diff --git a/modules/apigee_edge_actions/apigee_edge_actions.rules.events.yml b/modules/apigee_edge_actions/apigee_edge_actions.rules.events.yml new file mode 100644 index 00000000..3c7b37a5 --- /dev/null +++ b/modules/apigee_edge_actions/apigee_edge_actions.rules.events.yml @@ -0,0 +1,27 @@ +apigee_edge_actions_entity_insert: + deriver: '\Drupal\apigee_edge_actions\Plugin\RulesEvent\EdgeEntityInsertEventDeriver' + event: 'insert' + +apigee_edge_actions_entity_delete: + deriver: '\Drupal\apigee_edge_actions\Plugin\RulesEvent\EdgeEntityDeleteEventDeriver' + event: 'delete' + +apigee_edge_actions_entity_update: + deriver: '\Drupal\apigee_edge_actions\Plugin\RulesEvent\EdgeEntityUpdateEventDeriver' + event: 'update' + +apigee_edge_actions_entity_add_member: + deriver: '\Drupal\apigee_edge_actions\Plugin\RulesEvent\EdgeEntityAddMemberEventDeriver' + event: 'add_member' + +apigee_edge_actions_entity_remove_member: + deriver: '\Drupal\apigee_edge_actions\Plugin\RulesEvent\EdgeEntityRemoveMemberEventDeriver' + event: 'remove_member' + +apigee_edge_actions_entity_add_product: + deriver: '\Drupal\apigee_edge_actions\Plugin\RulesEvent\EdgeEntityAddProductEventDeriver' + event: 'add_product' + +apigee_edge_actions_entity_remove_product: + deriver: '\Drupal\apigee_edge_actions\Plugin\RulesEvent\EdgeEntityRemoveProductEventDeriver' + event: 'remove_product' diff --git a/modules/apigee_edge_actions/apigee_edge_actions.services.yml b/modules/apigee_edge_actions/apigee_edge_actions.services.yml new file mode 100644 index 00000000..070d4d83 --- /dev/null +++ b/modules/apigee_edge_actions/apigee_edge_actions.services.yml @@ -0,0 +1,13 @@ +services: + apigee_edge_actions.edge_entity_type_manager: + class: Drupal\apigee_edge_actions\ApigeeActionsEntityTypeHelper + arguments: ['@entity_type.manager'] + logger.channel.apigee_edge_actions: + parent: logger.channel_base + arguments: ['apigee_edge_actions'] + apigee_edge_actions.events_subscriber: + class: Drupal\apigee_edge_actions\EventSubscriber\AppCredentialEventSubscriber + arguments: + ['@entity_type.manager', '@event_dispatcher', '@current_user', '@logger.channel.apigee_edge_actions'] + tags: + - { name: 'event_subscriber' } diff --git a/modules/apigee_edge_actions/apigee_edge_actions.tokens.inc b/modules/apigee_edge_actions/apigee_edge_actions.tokens.inc new file mode 100644 index 00000000..a5e70643 --- /dev/null +++ b/modules/apigee_edge_actions/apigee_edge_actions.tokens.inc @@ -0,0 +1,98 @@ +getEntityTypes(); + $type_info = Drupal::service('plugin.manager.field.field_type')->getDefinitions(); + + foreach ($apigee_entity_types as $entity_type) { + $token_type = $entity_type->get('token_type'); + + if (!isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) { + $info['types'][$entity_type->id()] = [ + 'name' => $entity_type->getLabel(), + 'needs-data' => $entity_type->id(), + 'description' => t('Tokens related to @name.', [ + '@name' => $entity_type->getPluralLabel(), + ]), + 'module' => 'apigee_edge_actions', + ]; + + $fields = Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type->id()); + foreach ($fields as $field_name => $field) { + /** @var \Drupal\field\FieldStorageConfigInterface $field */ + $params['@type'] = $type_info[$field->getType()]['label']; + $description = t('@type field.', $params); + + $labels = _token_field_label($entity_type->id(), $field->getName()); + $label = array_shift($labels); + if (!empty($labels)) { + $params['%labels'] = implode(', ', $labels); + $description = t('@type field. Also known as %labels.', $params); + } + + $info['tokens'][$token_type][$field_name] = [ + 'name' => Html::escape($label), + 'description' => $description, + 'module' => 'token', + ]; + } + } + } +} + +/** + * Implements hook_tokens(). + */ +function apigee_edge_actions_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) { + $replacements = []; + + if ($type == 'entity' && !empty($data['entity_type']) && !empty($data['entity']) && !empty($data['token_type'])) { + /* @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = $data['entity']; + + if ($entity instanceof EdgeEntityInterface) { + foreach ($tokens as $field_name => $original) { + // Ensure entity has requested field and is not empty. + if (!$entity->hasField($field_name) || $entity->get($field_name)->isEmpty()) { + continue; + } + + $replacements[$original] = $entity->get($field_name)->value; + } + } + } + + return $replacements; +} diff --git a/modules/apigee_edge_actions/composer.json b/modules/apigee_edge_actions/composer.json new file mode 100644 index 00000000..1b515a52 --- /dev/null +++ b/modules/apigee_edge_actions/composer.json @@ -0,0 +1,16 @@ +{ + "name": "drupal/apigee_edge_actions", + "description": "Rules integration for Apigee Edge.", + "type": "drupal-module", + "license": "GPL-2.0-or-later", + "require": { + "php": ">=7.1", + "drupal/apigee_edge": "*", + "drupal/rules": "^3.0@alpha" + }, + "config": { + "sort-packages": true + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/modules/apigee_edge_actions/modules/apigee_edge_actions_debug/apigee_edge_actions_debug.info.yml b/modules/apigee_edge_actions/modules/apigee_edge_actions_debug/apigee_edge_actions_debug.info.yml new file mode 100644 index 00000000..f4dcbee3 --- /dev/null +++ b/modules/apigee_edge_actions/modules/apigee_edge_actions_debug/apigee_edge_actions_debug.info.yml @@ -0,0 +1,7 @@ +name: Apigee Edge Actions Debug +description: Logs debug information for Apigee Edge Actions. +package: Apigee (Experimental) +type: module +core: 8.x +dependencies: + - apigee_edge_actions:apigee_edge_actions diff --git a/modules/apigee_edge_actions/modules/apigee_edge_actions_debug/apigee_edge_actions_debug.services.yml b/modules/apigee_edge_actions/modules/apigee_edge_actions_debug/apigee_edge_actions_debug.services.yml new file mode 100644 index 00000000..4fcca814 --- /dev/null +++ b/modules/apigee_edge_actions/modules/apigee_edge_actions_debug/apigee_edge_actions_debug.services.yml @@ -0,0 +1,7 @@ +services: + apigee_edge_actions_debug_subscriber: + class: 'Drupal\apigee_edge_actions_debug\EventSubscriber\ApigeeEdgeActionsDebugEventSubscriber' + arguments: + ['@logger.channel.apigee_edge_actions'] + tags: + - { name: 'event_subscriber' } diff --git a/modules/apigee_edge_actions/modules/apigee_edge_actions_debug/src/EventSubscriber/ApigeeEdgeActionsDebugEventSubscriber.php b/modules/apigee_edge_actions/modules/apigee_edge_actions_debug/src/EventSubscriber/ApigeeEdgeActionsDebugEventSubscriber.php new file mode 100644 index 00000000..aa466470 --- /dev/null +++ b/modules/apigee_edge_actions/modules/apigee_edge_actions_debug/src/EventSubscriber/ApigeeEdgeActionsDebugEventSubscriber.php @@ -0,0 +1,86 @@ +logger = $logger; + } + + /** + * Responds to rules events. + * + * @param \Symfony\Component\EventDispatcher\Event $event + * The event object. + * @param string $event_name + * The event name. + */ + public function onRulesEvent(Event $event, $event_name) { + // Log the dispatched event. + if ($event instanceof ApigeeEdgeActionsEventInterface) { + $this->logger->notice("Event $event_name was dispatched."); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + // Ensure this is called after the container is built. + if (!\Drupal::hasService('state')) { + return []; + } + + $events = []; + + // Register a callback for all registered rules events. + if ($rules_events = \Drupal::state()->get('rules.registered_events')) { + foreach ($rules_events as $rules_event) { + $events[$rules_event][] = ['onRulesEvent', 100]; + } + } + + return $events; + } + +} diff --git a/modules/apigee_edge_actions/modules/apigee_edge_actions_examples/apigee_edge_actions_examples.info.yml b/modules/apigee_edge_actions/modules/apigee_edge_actions_examples/apigee_edge_actions_examples.info.yml new file mode 100644 index 00000000..19666cbe --- /dev/null +++ b/modules/apigee_edge_actions/modules/apigee_edge_actions_examples/apigee_edge_actions_examples.info.yml @@ -0,0 +1,11 @@ +name: Apigee Edge Actions Examples +description: Example rules for Apigee Edge. +package: Apigee (Experimental) +type: module +core: 8.x +configure: entity.rules_reaction_rule.collection +dependencies: + - apigee_edge_actions:apigee_edge_actions + - apigee_edge:apigee_edge + - apigee_edge_teams:apigee_edge_teams + - rules:rules diff --git a/modules/apigee_edge_actions/modules/apigee_edge_actions_examples/config/optional/rules.reaction.log_a_message_when_team_is_deleted.yml b/modules/apigee_edge_actions/modules/apigee_edge_actions_examples/config/optional/rules.reaction.log_a_message_when_team_is_deleted.yml new file mode 100644 index 00000000..01fff4e4 --- /dev/null +++ b/modules/apigee_edge_actions/modules/apigee_edge_actions_examples/config/optional/rules.reaction.log_a_message_when_team_is_deleted.yml @@ -0,0 +1,37 @@ +langcode: en +status: true +dependencies: { } +id: log_a_message_when_team_is_deleted +label: 'Log a message when team is deleted.' +events: + - + event_name: 'apigee_edge_actions_entity_delete:team' +description: 'Logs a message when a team is deleted.' +tags: + - example +config_version: '3' +expression: + id: rules_rule + uuid: 46a54411-060d-4b6d-bbef-ca7d3067ecc9 + conditions: + id: rules_and + uuid: b8fa61f6-ddb0-4a97-9729-617298ac6d8f + conditions: { } + actions: + id: rules_action_set + uuid: 525c38c8-cf9f-4424-b084-443079d8553a + actions: + - + id: rules_action + uuid: 632d4a81-8383-4343-82af-cabefb0824e0 + context_values: + message: 'The team {{ team.displayName }} was deleted.' + level: notice + context_mapping: { } + context_processors: + message: + rules_tokens: { } + level: + rules_tokens: { } + provides_mapping: { } + action_id: apigee_edge_actions_log_message diff --git a/modules/apigee_edge_actions/modules/apigee_edge_actions_examples/config/optional/rules.reaction.notify_developer_when_added_to_a_team.yml b/modules/apigee_edge_actions/modules/apigee_edge_actions_examples/config/optional/rules.reaction.notify_developer_when_added_to_a_team.yml new file mode 100644 index 00000000..3c0dbd1c --- /dev/null +++ b/modules/apigee_edge_actions/modules/apigee_edge_actions_examples/config/optional/rules.reaction.notify_developer_when_added_to_a_team.yml @@ -0,0 +1,47 @@ +langcode: en +status: true +dependencies: { } +id: notify_developer_when_added_to_a_team +label: 'Notify developer when added to a team' +events: + - + event_name: 'apigee_edge_actions_entity_add_member:team' +description: 'Sends an email to a developer when added to a team.' +tags: + - example +config_version: '3' +expression: + id: rules_rule + uuid: 0b36c5a1-b1f4-43ba-b3c1-b70859d22522 + conditions: + id: rules_and + uuid: 9f479df0-8312-49a4-bda3-e27091f13acb + conditions: { } + actions: + id: rules_action_set + uuid: fd94da7a-428d-446d-afa1-5e63725d3258 + actions: + - + id: rules_action + uuid: 5415ce2c-3238-4458-a472-2ac8097c07a1 + context_values: + to: + - '{{ member.mail }}' + subject: 'You have been added to the {{ team.displayName }} team.' + message: 'Hey {{ member.first_name }}, you have been added to the {{ team.displayName }} team.' + reply: '' + language: '' + context_mapping: { } + context_processors: + to: + rules_tokens: { } + subject: + rules_tokens: { } + message: + rules_tokens: { } + reply: + rules_tokens: { } + language: + rules_tokens: { } + provides_mapping: { } + action_id: rules_send_email diff --git a/modules/apigee_edge_actions/modules/apigee_edge_actions_examples/config/optional/rules.reaction.notify_developer_when_adding_a_new_app.yml b/modules/apigee_edge_actions/modules/apigee_edge_actions_examples/config/optional/rules.reaction.notify_developer_when_adding_a_new_app.yml new file mode 100644 index 00000000..032efcde --- /dev/null +++ b/modules/apigee_edge_actions/modules/apigee_edge_actions_examples/config/optional/rules.reaction.notify_developer_when_adding_a_new_app.yml @@ -0,0 +1,47 @@ +langcode: en +status: true +dependencies: { } +id: notify_developer_when_adding_a_new_app +label: 'Notify developer when adding a new app' +events: + - + event_name: 'apigee_edge_actions_entity_insert:developer_app' +description: 'Sends an email to the developer when adding a new app.' +tags: + - example +config_version: '3' +expression: + id: rules_rule + uuid: 9863eac4-9005-4185-a078-e979eb1e0668 + conditions: + id: rules_and + uuid: 0874eaac-3379-4b4c-8653-74ce89ae9c1f + conditions: { } + actions: + id: rules_action_set + uuid: 5f2f78a9-c970-4d7c-9ada-30dca4133d2d + actions: + - + id: rules_action + uuid: e4ec54f4-917a-413b-a410-8089296058f6 + context_values: + to: + - '{{ developer.mail }}' + subject: 'App {{ developer_app.displayName }} was created.' + message: 'App {{ developer_app.displayName }} was successfully created.' + reply: '' + language: '' + context_mapping: { } + context_processors: + to: + rules_tokens: { } + subject: + rules_tokens: { } + message: + rules_tokens: { } + reply: + rules_tokens: { } + language: + rules_tokens: { } + provides_mapping: { } + action_id: rules_send_email diff --git a/modules/apigee_edge_actions/modules/apigee_edge_actions_examples/config/optional/rules.reaction.notify_site_admins_when_app_is_created.yml b/modules/apigee_edge_actions/modules/apigee_edge_actions_examples/config/optional/rules.reaction.notify_site_admins_when_app_is_created.yml new file mode 100644 index 00000000..27c69c99 --- /dev/null +++ b/modules/apigee_edge_actions/modules/apigee_edge_actions_examples/config/optional/rules.reaction.notify_site_admins_when_app_is_created.yml @@ -0,0 +1,47 @@ +langcode: en +status: true +dependencies: { } +id: notify_site_admins_when_app_is_created +label: 'Notify site admins when app is created' +events: + - + event_name: 'apigee_edge_actions_entity_insert:developer_app' +description: 'Send an email to site administrators when an app is created.' +tags: + - example +config_version: '3' +expression: + id: rules_rule + uuid: a3c7f453-75a4-4d1b-87f7-973831d18365 + conditions: + id: rules_and + uuid: 3e95f490-4379-412e-9bf3-2a2b8b68e6e3 + conditions: { } + actions: + id: rules_action_set + uuid: ea47410d-065e-4cf4-8f8e-9fafdcdbee9b + actions: + - + id: rules_action + uuid: b2826e2c-5322-4dfe-9020-fc622f0bbf2e + context_values: + roles: + - administrator + subject: 'App {{ developer_app.displayName }} was added to the site.' + message: 'App {{ developer_app.displayName }} was added to the site.' + reply: '' + language: '' + context_mapping: { } + context_processors: + roles: + rules_tokens: { } + subject: + rules_tokens: { } + message: + rules_tokens: { } + reply: + rules_tokens: { } + language: + rules_tokens: { } + provides_mapping: { } + action_id: rules_email_to_users_of_role diff --git a/modules/apigee_edge_actions/src/ApigeeActionsEntityTypeHelper.php b/modules/apigee_edge_actions/src/ApigeeActionsEntityTypeHelper.php new file mode 100644 index 00000000..a246ac1d --- /dev/null +++ b/modules/apigee_edge_actions/src/ApigeeActionsEntityTypeHelper.php @@ -0,0 +1,65 @@ +entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public function getEntityTypes(): array { + return array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entity_type) { + return $this->isFieldableEdgeEntityType($entity_type); + }); + } + + /** + * {@inheritdoc} + */ + public function isFieldableEdgeEntityType(EntityTypeInterface $entity_type): bool { + return $entity_type->entityClassImplements(FieldableEdgeEntityInterface::class); + } + +} diff --git a/modules/apigee_edge_actions/src/ApigeeActionsEntityTypeHelperInterface.php b/modules/apigee_edge_actions/src/ApigeeActionsEntityTypeHelperInterface.php new file mode 100644 index 00000000..c3e36be0 --- /dev/null +++ b/modules/apigee_edge_actions/src/ApigeeActionsEntityTypeHelperInterface.php @@ -0,0 +1,49 @@ +has('apigee_edge_teams.team_membership_manager')) { + $container->register('apigee_edge_actions.team_membership_manager', TeamMembershipManager::class) + ->setDecoratedService('apigee_edge_teams.team_membership_manager') + ->setArguments([ + new Reference('apigee_edge_actions.team_membership_manager.inner'), + new Reference('entity_type.manager'), + new Reference('apigee_edge_teams.company_members_controller_factory'), + new Reference('apigee_edge.controller.developer'), + new Reference('apigee_edge.controller.cache.developer_companies'), + new Reference('cache_tags.invalidator'), + new Reference('logger.channel.apigee_edge_teams'), + new Reference('event_dispatcher'), + ]); + } + } + +} diff --git a/modules/apigee_edge_actions/src/Event/ApigeeEdgeActionsEventInterface.php b/modules/apigee_edge_actions/src/Event/ApigeeEdgeActionsEventInterface.php new file mode 100644 index 00000000..a06c7b83 --- /dev/null +++ b/modules/apigee_edge_actions/src/Event/ApigeeEdgeActionsEventInterface.php @@ -0,0 +1,28 @@ +entityTypeManger = $entity_type_manager; + $this->logger = $logger; + $this->eventDispatcher = $event_dispatcher; + $this->currentUser = $current_user; + } + + /** + * Responds to add product events. + * + * @param \Drupal\apigee_edge\Event\AppCredentialAddApiProductEvent $event + * The app credential add product event. + */ + public function onAddProduct(AppCredentialAddApiProductEvent $event) { + $this->dispatchRulesEvent('apigee_edge_actions_entity_add_product:developer_app', $event, $event->getNewProducts()); + } + + /** + * Responds to remove product events. + * + * @param \Drupal\apigee_edge\Event\AppCredentialDeleteApiProductEvent $event + * The app credential remove product event. + */ + public function onRemoveProduct(AppCredentialDeleteApiProductEvent $event) { + $this->dispatchRulesEvent('apigee_edge_actions_entity_remove_product:developer_app', $event, [$event->getApiProduct()]); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + AppCredentialAddApiProductEvent::EVENT_NAME => ['onAddProduct', 100], + AppCredentialDeleteApiProductEvent::EVENT_NAME => ['onRemoveProduct', 100], + ]; + } + + /** + * Helper to dispatch a corresponding rules event for an api credential event. + * + * @param string $rules_event_name + * The name of the rules event. + * @param \Symfony\Component\EventDispatcher\Event $event + * The api credential event. + * @param array $api_products + * An array of api products. + */ + protected function dispatchRulesEvent(string $rules_event_name, Event $event, array $api_products) { + try { + $app = $this->getAppByName($event->getAppName(), $event->getOwnerId(), $event->getAppType()); + $app_type = "{$event->getAppType()}_app"; + + if ('developer_app' == $app_type) { + // For developer apps, get the Drupal account from the app owner. + /** @var \Drupal\apigee_edge\Entity\Storage\DeveloperStorageInterface $developer_storage */ + /** @var \Drupal\apigee_edge\Entity\Developer $owner */ + $developer_storage = $this->entityTypeManger->getStorage($event->getAppType()); + $owner = $developer_storage->load($event->getOwnerId()); + $developer = user_load_by_mail($owner->getEmail()); + } + else { + // For team apps, default to the current user. + $developer = $this->entityTypeManger->getStorage('user') + ->load($this->currentUser->id()); + } + + foreach ($api_products as $product) { + /** @var \Drupal\apigee_edge\Entity\ApiProductInterface $api_product */ + $api_product = $this->entityTypeManger + ->getStorage('api_product') + ->load($product); + $this->eventDispatcher->dispatch($rules_event_name, new EdgeEntityEventEdge($app, [ + $app_type => $app, + 'developer' => $developer, + 'api_product_name' => $api_product->getName(), + 'api_product_display_name' => $api_product->getDisplayName(), + ])); + } + } + catch (PluginException $exception) { + $this->logger->error($exception->getMessage()); + } + } + + /** + * Helper to load an app by name. + * + * @param string $name + * The name of the app. + * @param string $owner_id + * The developer or team. + * @param string $app_type + * The type of the app. + * + * @return \Drupal\apigee_edge\Entity\AppInterface|null + * The app with the provided name or null. + */ + protected function getAppByName(string $name, string $owner_id, string $app_type): ?AppInterface { + /* @var \Drupal\apigee_edge\Entity\AppInterface $appClass */ + $appClass = $this->entityTypeManger->getStorage("{$app_type}_app")->getEntityType()->getClass(); + + try { + if ($app_type == 'developer') { + /* @var \Drupal\apigee_edge\Entity\Controller\DeveloperAppControllerFactoryInterface $controller */ + $controller = \Drupal::service('apigee_edge.controller.developer_app_controller_factory'); + $edge_app = $controller->developerAppController($owner_id)->load($name); + } + else { + /* @var \Drupal\apigee_edge_teams\Entity\Controller\TeamAppControllerFactory $controller */ + $controller = \Drupal::service('apigee_edge_teams.controller.team_app_controller_factory'); + $edge_app = $controller->teamAppController($owner_id)->load($name); + } + + $app = $appClass::createFrom($edge_app); + + return $app; + } + catch (PluginException $exception) { + $this->logger->error($exception); + } + + return NULL; + } + +} diff --git a/modules/apigee_edge_actions/src/Plugin/RulesAction/LogMessage.php b/modules/apigee_edge_actions/src/Plugin/RulesAction/LogMessage.php new file mode 100644 index 00000000..4b3f6ec0 --- /dev/null +++ b/modules/apigee_edge_actions/src/Plugin/RulesAction/LogMessage.php @@ -0,0 +1,101 @@ +logger = $logger; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('logger.channel.apigee_edge_actions') + ); + } + + /** + * Executes the action. + * + * @param string $message + * The message. + * @param string $level + * The log level. + */ + protected function doExecute(string $message, string $level = "notice") { + if (method_exists($this->logger, $level)) { + $this->logger->{$level}($message); + } + } + +} diff --git a/modules/apigee_edge_actions/src/Plugin/RulesAction/SystemMailToUsersOfRole.php b/modules/apigee_edge_actions/src/Plugin/RulesAction/SystemMailToUsersOfRole.php new file mode 100644 index 00000000..7cae4509 --- /dev/null +++ b/modules/apigee_edge_actions/src/Plugin/RulesAction/SystemMailToUsersOfRole.php @@ -0,0 +1,92 @@ +roleStorage = $role_storage; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('logger.factory')->get('rules'), + $container->get('plugin.manager.mail'), + $container->get('entity_type.manager')->getStorage('user'), + $container->get('entity_type.manager')->getStorage('user_role') + ); + } + + /** + * {@inheritdoc} + */ + protected function doExecute(array $roles, $subject, $message, $reply = NULL, LanguageInterface $language = NULL) { + // SystemMailToUsersOfRole::doExecute() expects an array of RoleInterface. + // Upcast $roles from string[] to RoleInterface[]. + // @see https://www.drupal.org/project/rules/issues/2800749 + $roles = $this->roleStorage->loadMultiple($roles); + parent::doExecute($roles, $subject, $message, $reply, $language); + } + +} diff --git a/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityAddMemberEventDeriver.php b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityAddMemberEventDeriver.php new file mode 100644 index 00000000..c557f747 --- /dev/null +++ b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityAddMemberEventDeriver.php @@ -0,0 +1,63 @@ +t('After adding a team member'); + } + + /** + * {@inheritdoc} + */ + public function getEntityTypes(): array { + // Filter out non team entity types. + return array_filter(parent::getEntityTypes(), function (EdgeEntityTypeInterface $entity_type) { + return $entity_type->entityClassImplements(TeamInterface::class); + }); + } + + /** + * {@inheritdoc} + */ + public function getContext(EdgeEntityTypeInterface $entity_type): array { + $context = parent::getContext($entity_type); + + // Add the team member to the context. + $context['member'] = [ + 'type' => 'entity:user', + 'label' => $this->t('Member'), + ]; + + return $context; + } + +} diff --git a/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityAddProductEventDeriver.php b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityAddProductEventDeriver.php new file mode 100644 index 00000000..3f558c07 --- /dev/null +++ b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityAddProductEventDeriver.php @@ -0,0 +1,37 @@ +t('After adding an API product'); + } + +} diff --git a/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityDeleteEventDeriver.php b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityDeleteEventDeriver.php new file mode 100644 index 00000000..52c9fbfd --- /dev/null +++ b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityDeleteEventDeriver.php @@ -0,0 +1,37 @@ +t('After deleting a @entity_type', ['@entity_type' => $entity_type->getSingularLabel()]); + } + +} diff --git a/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityEventDeriverBase.php b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityEventDeriverBase.php new file mode 100644 index 00000000..fedf477f --- /dev/null +++ b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityEventDeriverBase.php @@ -0,0 +1,118 @@ +edgeEntityTypeManager = $edge_entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, $base_plugin_id) { + return new static( + $container->get('apigee_edge_actions.edge_entity_type_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function getEntityTypes(): array { + return $this->edgeEntityTypeManager->getEntityTypes(); + } + + /** + * {@inheritdoc} + */ + public function getContext(EdgeEntityTypeInterface $entity_type): array { + $context = [ + $entity_type->id() => [ + 'type' => "entity:{$entity_type->id()}", + 'label' => $entity_type->getLabel(), + ], + ]; + + // Add additional context for App. + if ($entity_type->entityClassImplements(AppInterface::class)) { + // Add the developer to the context. + $context['developer'] = [ + 'type' => 'entity:user', + 'label' => 'Developer', + ]; + + // Add the team to the context. + if ($entity_type->entityClassImplements(TeamAppInterface::class)) { + $context['team'] = [ + 'type' => 'entity:team', + 'label' => 'Team', + ]; + } + } + + return $context; + } + + /** + * {@inheritdoc} + */ + public function getDerivativeDefinitions($base_plugin_definition) { + foreach ($this->getEntityTypes() as $entity_type) { + $this->derivatives[$entity_type->id()] = [ + 'label' => $this->getLabel($entity_type), + 'category' => $entity_type->getLabel(), + 'entity_type_id' => $entity_type->id(), + 'context' => $this->getContext($entity_type), + ] + $base_plugin_definition; + } + + return $this->derivatives; + } + +} diff --git a/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityEventDeriverInterface.php b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityEventDeriverInterface.php new file mode 100644 index 00000000..1ea24d90 --- /dev/null +++ b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityEventDeriverInterface.php @@ -0,0 +1,61 @@ +t('After saving a new @entity_type', ['@entity_type' => $entity_type->getSingularLabel()]); + } + +} diff --git a/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityProductEventDeriverBase.php b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityProductEventDeriverBase.php new file mode 100644 index 00000000..5f0aeae1 --- /dev/null +++ b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityProductEventDeriverBase.php @@ -0,0 +1,63 @@ +entityClassImplements(AppInterface::class); + }); + } + + /** + * {@inheritdoc} + */ + public function getContext(EdgeEntityTypeInterface $entity_type): array { + $context = parent::getContext($entity_type); + + // The api_product entity type is not fieldable hence does not support typed + // data. We have to add the attributes individually here. + $context['api_product_name'] = [ + 'type' => 'string', + 'label' => $this->t('Name'), + ]; + $context['api_product_display_name'] = [ + 'type' => 'string', + 'label' => $this->t('Display name'), + ]; + + return $context; + } + +} diff --git a/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityRemoveMemberEventDeriver.php b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityRemoveMemberEventDeriver.php new file mode 100644 index 00000000..cda50a1c --- /dev/null +++ b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityRemoveMemberEventDeriver.php @@ -0,0 +1,63 @@ +t('After removing a team member'); + } + + /** + * {@inheritdoc} + */ + public function getEntityTypes(): array { + // Filter out non team entity types. + return array_filter(parent::getEntityTypes(), function (EdgeEntityTypeInterface $entity_type) { + return $entity_type->entityClassImplements(TeamInterface::class); + }); + } + + /** + * {@inheritdoc} + */ + public function getContext(EdgeEntityTypeInterface $entity_type): array { + $context = parent::getContext($entity_type); + + // Add the team member to the context. + $context['member'] = [ + 'type' => 'entity:user', + 'label' => $this->t('Member'), + ]; + + return $context; + } + +} diff --git a/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityRemoveProductEventDeriver.php b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityRemoveProductEventDeriver.php new file mode 100644 index 00000000..3309f975 --- /dev/null +++ b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityRemoveProductEventDeriver.php @@ -0,0 +1,37 @@ +t('After removing an API product'); + } + +} diff --git a/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityUpdateEventDeriver.php b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityUpdateEventDeriver.php new file mode 100644 index 00000000..5a7a0e65 --- /dev/null +++ b/modules/apigee_edge_actions/src/Plugin/RulesEvent/EdgeEntityUpdateEventDeriver.php @@ -0,0 +1,52 @@ +t('After updating a @entity_type', ['@entity_type' => $entity_type->getSingularLabel()]); + } + + /** + * {@inheritdoc} + */ + public function getContext(EdgeEntityTypeInterface $entity_type): array { + $context = parent::getContext($entity_type); + + // Add the original entity to the context. + $context["{$entity_type->id()}_unchanged"] = [ + 'type' => "entity:{$entity_type->id()}", + 'label' => $this->t('Unchanged @entity_type', ['@entity_type' => $entity_type->getLabel()]), + ]; + + return $context; + } + +} diff --git a/modules/apigee_edge_actions/src/TeamMembershipManager.php b/modules/apigee_edge_actions/src/TeamMembershipManager.php new file mode 100644 index 00000000..03a7ebcf --- /dev/null +++ b/modules/apigee_edge_actions/src/TeamMembershipManager.php @@ -0,0 +1,187 @@ +inner = $inner; + $this->entityTypeManager = $entity_type_manager; + $this->companyMembersControllerFactory = $company_members_controller_factory; + $this->developerController = $developer_controller; + $this->developerCompaniesCache = $developer_companies_cache; + $this->cacheTagsInvalidator = $cache_tags_invalidator; + $this->logger = $logger; + $this->eventDispatcher = $event_dispatcher; + } + + /** + * {@inheritdoc} + */ + public function getMembers(string $team): array { + return $this->inner->getMembers($team); + } + + /** + * {@inheritdoc} + */ + public function addMembers(string $team, array $developers): void { + $this->inner->addMembers($team, $developers); + + $this->dispatchEvent('apigee_edge_actions_entity_add_member:team', $team, $developers); + } + + /** + * {@inheritdoc} + */ + public function removeMembers(string $team, array $developers): void { + $this->inner->removeMembers($team, $developers); + + $this->dispatchEvent('apigee_edge_actions_entity_remove_member:team', $team, $developers); + } + + /** + * {@inheritdoc} + */ + public function getTeams(string $developer): array { + return $this->inner->getTeams($developer); + } + + /** + * Helper to dispatch event. + * + * @param string $event + * The event name. + * @param string $team + * The team id. + * @param array $developers + * An array of developers. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + protected function dispatchEvent(string $event, string $team, array $developers) { + $team = $this->entityTypeManager->getStorage('team')->load($team); + $users_by_mail = array_reduce($this->entityTypeManager->getStorage('user')->loadByProperties(['mail' => $developers]), function (array $carry, UserInterface $user) { + $carry[$user->getEmail()] = $user; + return $carry; + }, []); + + // Dispatch an event for each developer. + foreach ($developers as $developer) { + $this->eventDispatcher->dispatch($event, new EdgeEntityEventEdge($team, [ + 'team' => $team, + 'member' => $users_by_mail[$developer], + ])); + } + } + +} diff --git a/modules/apigee_edge_actions/tests/src/Kernel/ApigeeEdgeActionsRulesKernelTestBase.php b/modules/apigee_edge_actions/tests/src/Kernel/ApigeeEdgeActionsRulesKernelTestBase.php new file mode 100644 index 00000000..5f8744c1 --- /dev/null +++ b/modules/apigee_edge_actions/tests/src/Kernel/ApigeeEdgeActionsRulesKernelTestBase.php @@ -0,0 +1,116 @@ +storage = $this->container->get('entity_type.manager')->getStorage('rules_reaction_rule'); + + $this->installConfig(['apigee_edge']); + $this->installEntitySchema('user'); + $this->installSchema('dblog', ['watchdog']); + $this->installSchema('system', ['sequences']); + $this->installSchema('user', ['users_data']); + + $this->baseSetUp(); + + /** @var \Drupal\user\UserInterface $account */ + $this->account = User::create([ + 'mail' => $this->randomMachineName() . '@example.com', + 'name' => $this->randomMachineName(), + 'first_name' => $this->getRandomGenerator()->word(16), + 'last_name' => $this->getRandomGenerator()->word(16), + ]); + $this->account->save(); + $this->queueDeveloperResponse($this->account, Response::HTTP_CREATED); + } + + /** + * Helper to assert logs. + * + * @param string $message + * The message to assert in the logs. + * @param string $type + * The type for the log. + */ + protected function assertLogsContains(string $message, $type = 'apigee_edge_actions') { + $logs = Database::getConnection()->select('watchdog', 'wd') + ->fields('wd', ['message', 'variables']) + ->condition('type', $type) + ->execute() + ->fetchAll(); + + $controller = DbLogController::create($this->container); + $messages = array_map(function ($log) use ($controller) { + return (string) $controller->formatMessage($log); + }, $logs); + + $this->assertContains($message, $messages); + } + +} diff --git a/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesAction/SystemMailToUsersOfRoleTest.php b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesAction/SystemMailToUsersOfRoleTest.php new file mode 100644 index 00000000..fd359d3d --- /dev/null +++ b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesAction/SystemMailToUsersOfRoleTest.php @@ -0,0 +1,75 @@ +container->get('entity_type.manager')->getStorage('user_role'); + $role_storage->create(['id' => 'test_role'])->save(); + $this->account->addRole('test_role'); + $this->queueDeveloperResponse($this->account); + $this->account->activate(); + $this->account->save(); + + $rule = $this->expressionManager->createRule(); + $rule->addAction('rules_email_to_users_of_role', + ContextConfig::create() + ->setValue('roles', ['test_role']) + ->setValue('subject', 'Test email') + ->setValue('message', 'This is a test email') + ); + + $config_entity = $this->storage->create([ + 'id' => 'send_email_to_admin_rule', + 'events' => [['event_name' => 'apigee_edge_actions_entity_insert:developer_app']], + 'expression' => $rule->getConfiguration(), + ]); + $config_entity->save(); + + // Insert an entity to trigger rule. + $this->queueDeveloperResponse($this->account); + $this->createDeveloperApp(); + + $this->assertLogsContains("Event apigee_edge_actions_entity_insert:developer_app was dispatched."); + $this->assertLogsContains('Successfully sent email to 1 out of 1 users having the role(s) test_role', 'rules'); + } + +} diff --git a/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityAddMemberEventTest.php b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityAddMemberEventTest.php new file mode 100644 index 00000000..d61f1ff1 --- /dev/null +++ b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityAddMemberEventTest.php @@ -0,0 +1,95 @@ +installEntitySchema('team_member_role'); + } + + /** + * Tests add_member events for Edge entities. + * + * @throws \Drupal\Core\Entity\EntityStorageException + * @throws \Drupal\rules\Exception\LogicException + */ + public function testEvent() { + // Create an add_member rule. + $rule = $this->expressionManager->createRule(); + $rule->addAction('apigee_edge_actions_log_message', + ContextConfig::create() + ->setValue('message', "Member {{ member.first_name }} was added to team {{ team.displayName }}.") + ->process('message', 'rules_tokens') + ); + + $config_entity = $this->storage->create([ + 'id' => 'app_add_member_rule', + 'events' => [['event_name' => 'apigee_edge_actions_entity_add_member:team']], + 'expression' => $rule->getConfiguration(), + ]); + $config_entity->save(); + + // Create a new team. + $team = $this->createTeam(); + + // Add team member. + $this->queueCompanyResponse($team->decorated()); + $this->queueDeveloperResponse($this->account); + $this->container->get('apigee_edge_teams.team_membership_manager')->addMembers($team->id(), [ + $this->account->getEmail(), + ]); + + $this->assertLogsContains("Event apigee_edge_actions_entity_add_member:team was dispatched."); + $this->assertLogsContains("Member {$this->account->first_name->value} was added to team {$team->getDisplayName()}."); + } + +} diff --git a/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityAddProductEventTest.php b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityAddProductEventTest.php new file mode 100644 index 00000000..8b4d9288 --- /dev/null +++ b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityAddProductEventTest.php @@ -0,0 +1,118 @@ +randomMachineName(); + // Create an insert rule. + $rule = $this->expressionManager->createRule(); + $rule->addAction('apigee_edge_actions_log_message', + ContextConfig::create() + ->setValue('message', "Product {{ api_product_name }} was added to app {{ developer_app.name }}.") + ->process('message', 'rules_tokens') + ); + + // Test condition. + $rule->addCondition('rules_data_comparison', ContextConfig::create() + ->map('data', 'api_product_name') + ->setValue('value', $api_product_name) + ); + + $config_entity = $this->storage->create([ + 'id' => 'app_insert_rule', + 'events' => [['event_name' => 'apigee_edge_actions_entity_add_product:developer_app']], + 'expression' => $rule->getConfiguration(), + ]); + $config_entity->save(); + + /** @var \Drupal\apigee_edge\Entity\AppInterface $developer_app */ + $developer_app = $this->createDeveloperApp(); + + $api_product = ApiProduct::create([ + 'name' => $api_product_name, + 'displayName' => $this->getRandomGenerator()->word(16), + 'approvalType' => ApiProduct::APPROVAL_TYPE_AUTO, + ]); + + /** @var \Drupal\apigee_edge\Entity\ApiProduct $api_product */ + $this->stack->queueMockResponse([ + 'api_product' => [ + 'product' => $api_product, + ], + ]); + + $api_product->save(); + + $this->stack->queueMockResponse([ + 'api_product' => [ + 'product' => $api_product, + ], + ]); + $this->queueDeveloperResponse($this->account); + $this->stack->queueMockResponse([ + 'get_developer_apps' => [ + 'apps' => [$developer_app] + ], + ]); + + /** @var \Drupal\apigee_edge\Entity\Controller\DeveloperAppCredentialControllerFactoryInterface $credential_factory */ + $credential_factory = \Drupal::service('apigee_edge.controller.developer_app_credential_factory'); + $credential_factory->developerAppCredentialController($this->account->uuid(), $developer_app->getName())->addProducts($this->randomString(), [$api_product->id()]); + + $this->assertLogsContains("Event apigee_edge_actions_entity_add_product:developer_app was dispatched."); + $this->assertLogsContains("Product {$api_product->getName()} was added to app {$developer_app->getName()}."); + } + +} diff --git a/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityDeleteEventTest.php b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityDeleteEventTest.php new file mode 100644 index 00000000..7c86bfd2 --- /dev/null +++ b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityDeleteEventTest.php @@ -0,0 +1,67 @@ +expressionManager->createRule(); + $rule->addAction('apigee_edge_actions_log_message', + ContextConfig::create() + ->setValue('message', "App {{ developer_app.name }} was deleted.") + ->process('message', 'rules_tokens') + ); + + $config_entity = $this->storage->create([ + 'id' => 'app_delete_rule', + 'events' => [['event_name' => 'apigee_edge_actions_entity_delete:developer_app']], + 'expression' => $rule->getConfiguration(), + ]); + $config_entity->save(); + + // Insert and delete entity. + $entity = $this->createDeveloperApp(); + $this->queueDeveloperAppResponse($entity); + $entity->delete(); + + $this->assertLogsContains("Event apigee_edge_actions_entity_delete:developer_app was dispatched."); + $this->assertLogsContains("App {$entity->getName()} was deleted."); + } + +} diff --git a/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityInsertEventTest.php b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityInsertEventTest.php new file mode 100644 index 00000000..3942b2fd --- /dev/null +++ b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityInsertEventTest.php @@ -0,0 +1,65 @@ +expressionManager->createRule(); + $rule->addAction('apigee_edge_actions_log_message', + ContextConfig::create() + ->setValue('message', "App {{ developer_app.name }} was created by {{ developer.first_name }}.") + ->process('message', 'rules_tokens') + ); + + $config_entity = $this->storage->create([ + 'id' => 'app_insert_rule', + 'events' => [['event_name' => 'apigee_edge_actions_entity_insert:developer_app']], + 'expression' => $rule->getConfiguration(), + ]); + $config_entity->save(); + + // Insert an entity. + $entity = $this->createDeveloperApp(); + + $this->assertLogsContains("Event apigee_edge_actions_entity_insert:developer_app was dispatched."); + $this->assertLogsContains("App {$entity->getName()} was created by {$this->account->first_name->value}."); + } + +} diff --git a/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityRemoveMemberEventTest.php b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityRemoveMemberEventTest.php new file mode 100644 index 00000000..62387ec7 --- /dev/null +++ b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityRemoveMemberEventTest.php @@ -0,0 +1,100 @@ +installEntitySchema('team_member_role'); + } + + /** + * Tests add_member events for Edge entities. + * + * @throws \Drupal\Core\Entity\EntityStorageException + * @throws \Drupal\rules\Exception\LogicException + */ + public function testEvent() { + // Create an remove_member rule. + $rule = $this->expressionManager->createRule(); + $rule->addAction('apigee_edge_actions_log_message', + ContextConfig::create() + ->setValue('message', "Member {{ member.first_name }} was removed from team {{ team.displayName }}.") + ->process('message', 'rules_tokens') + ); + + $config_entity = $this->storage->create([ + 'id' => 'app_remove_member_rule', + 'events' => [['event_name' => 'apigee_edge_actions_entity_remove_member:team']], + 'expression' => $rule->getConfiguration(), + ]); + $config_entity->save(); + + // Create a new team. + $team = $this->createTeam(); + + // Add team member. + $this->queueCompanyResponse($team->decorated()); + $this->queueDeveloperResponse($this->account); + $team_membership_manager = $this->container->get('apigee_edge_teams.team_membership_manager'); + $team_membership_manager->addMembers($team->id(), [ + $this->account->getEmail(), + ]); + + // Remove team member. + $team_membership_manager->removeMembers($team->id(), [ + $this->account->getEmail(), + ]); + + $this->assertLogsContains("Event apigee_edge_actions_entity_remove_member:team was dispatched."); + $this->assertLogsContains("Member {$this->account->first_name->value} was removed from team {$team->getDisplayName()}."); + } + +} diff --git a/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityRemoveProductEventTest.php b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityRemoveProductEventTest.php new file mode 100644 index 00000000..10b06000 --- /dev/null +++ b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityRemoveProductEventTest.php @@ -0,0 +1,122 @@ +expressionManager->createRule(); + $rule->addAction('apigee_edge_actions_log_message', + ContextConfig::create() + ->setValue('message', "Product {{ api_product_name }} was removed from app {{ developer_app.name }}.") + ->process('message', 'rules_tokens') + ); + + $config_entity = $this->storage->create([ + 'id' => 'app_insert_rule', + 'events' => [['event_name' => 'apigee_edge_actions_entity_remove_product:developer_app']], + 'expression' => $rule->getConfiguration(), + ]); + $config_entity->save(); + + /** @var \Drupal\apigee_edge\Entity\AppInterface $developer_app */ + $developer_app = $this->createDeveloperApp(); + + $api_product = ApiProduct::create([ + 'name' => $this->randomMachineName(), + 'displayName' => $this->getRandomGenerator()->word(16), + 'approvalType' => ApiProduct::APPROVAL_TYPE_AUTO, + ]); + + /** @var \Drupal\apigee_edge\Entity\ApiProduct $api_product */ + $this->stack->queueMockResponse([ + 'api_product' => [ + 'product' => $api_product, + ], + ]); + + $api_product->save(); + + $this->stack->queueMockResponse([ + 'api_product' => [ + 'product' => $api_product, + ], + ]); + $this->queueDeveloperResponse($this->account); + $this->queueDeveloperAppResponse($developer_app); + + /** @var \Drupal\apigee_edge\Entity\Controller\DeveloperAppCredentialControllerFactoryInterface $credential_factory */ + $credential_factory = \Drupal::service('apigee_edge.controller.developer_app_credential_factory'); + /** @var \Drupal\apigee_edge\Entity\Controller\AppCredentialControllerInterface $app_credential_controller */ + $app_credential_controller = $credential_factory->developerAppCredentialController($this->account->uuid(), $developer_app->getName()); + $consumer_key = $this->randomString(); + $app_credential_controller->addProducts($consumer_key, [$api_product->id()]); + + $this->stack->queueMockResponse([ + 'api_product' => [ + 'product' => $api_product, + ], + ]); + $this->stack->queueMockResponse([ + 'get_developer_apps' => [ + 'apps' => [$developer_app] + ], + ]); + $app_credential_controller->deleteApiProduct($consumer_key, $api_product->id()); + + $this->assertLogsContains("Event apigee_edge_actions_entity_remove_product:developer_app was dispatched."); + $this->assertLogsContains("Product {$api_product->getName()} was removed from app {$developer_app->getName()}."); + } + +} diff --git a/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityUpdateEventTest.php b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityUpdateEventTest.php new file mode 100644 index 00000000..7503c678 --- /dev/null +++ b/modules/apigee_edge_actions/tests/src/Kernel/Plugin/RulesEvent/EdgeEntityUpdateEventTest.php @@ -0,0 +1,72 @@ +expressionManager->createRule(); + $rule->addAction('apigee_edge_actions_log_message', + ContextConfig::create() + ->setValue('message', "App {{ developer_app_unchanged.displayName }} was renamed to {{ developer_app.displayName }}.") + ->process('message', 'rules_tokens') + ); + + $config_entity = $this->storage->create([ + 'id' => 'app_update_rule', + 'events' => [['event_name' => 'apigee_edge_actions_entity_update:developer_app']], + 'expression' => $rule->getConfiguration(), + ]); + $config_entity->save(); + + // Insert and update entity. + /** @var \Drupal\apigee_edge\Entity\DeveloperAppInterface $entity */ + $entity = $this->createDeveloperApp(); + $original_name = $entity->getDisplayName(); + $new_name = $this->randomGenerator->name(); + $this->queueDeveloperAppResponse($entity); + $entity->setDisplayName($new_name); + $this->queueDeveloperAppResponse($entity); + $entity->save(); + + $this->assertLogsContains("Event apigee_edge_actions_entity_update:developer_app was dispatched."); + $this->assertLogsContains("App $original_name was renamed to $new_name."); + } + +} diff --git a/tests/modules/apigee_mock_api_client/tests/response-templates/get-developer-apps.json.twig b/tests/modules/apigee_mock_api_client/tests/response-templates/get-developer-apps.json.twig new file mode 100644 index 00000000..dbb2bd4b --- /dev/null +++ b/tests/modules/apigee_mock_api_client/tests/response-templates/get-developer-apps.json.twig @@ -0,0 +1,19 @@ +{# +/** + * @file + * GET /v1/organizations/{org_name}/developers/{developer_email_or_id}/apps + * + * Response Code: 200 + * + * Variables: + * - org_name: The name of the org. + * - developer_email_or_id: The developer email or id. + */ +#} +{ + "apps" : [ + {% for app in apps %} + {% include 'developer-app.json.twig' with {'app': app} %}{{ loop.last ? '' : ',' }} + {% endfor %} + ] +} diff --git a/tests/modules/apigee_mock_api_client/tests/src/Traits/ApigeeMockApiClientHelperTrait.php b/tests/modules/apigee_mock_api_client/tests/src/Traits/ApigeeMockApiClientHelperTrait.php index 8faada70..fac9f1f3 100644 --- a/tests/modules/apigee_mock_api_client/tests/src/Traits/ApigeeMockApiClientHelperTrait.php +++ b/tests/modules/apigee_mock_api_client/tests/src/Traits/ApigeeMockApiClientHelperTrait.php @@ -19,12 +19,16 @@ namespace Drupal\Tests\apigee_mock_api_client\Traits; +use Apigee\Edge\Api\Management\Entity\App; use Apigee\Edge\Api\Management\Entity\Company; use Apigee\Edge\Api\Management\Entity\Organization; use Apigee\MockClient\Generator\ApigeeSdkEntitySource; use Drupal\apigee_edge\Entity\Developer; +use Drupal\apigee_edge\Entity\DeveloperApp; use Drupal\apigee_edge\Entity\DeveloperAppInterface; use Drupal\apigee_edge\Entity\DeveloperInterface; +use Drupal\apigee_edge_teams\Entity\Team; +use Drupal\apigee_edge_teams\Entity\TeamInterface; use Drupal\Tests\apigee_edge\Traits\ApigeeEdgeUtilTestTrait; use Drupal\user\UserInterface; use Http\Message\RequestMatcher\RequestMatcher; @@ -223,7 +227,51 @@ protected function queueDevsInCompanyResponse(array $developers, $response_code } /** - * Add an app analytics mock response to the stack. + * Helper to create a DeveloperApp entity. + * + * @return \Drupal\apigee_edge\Entity\DeveloperAppInterface + * A DeveloperApp entity. + * + * @throws \Drupal\Core\Entity\EntityStorageException + */ + protected function createDeveloperApp(): DeveloperAppInterface { + /** @var \Drupal\apigee_edge\Entity\DeveloperAppInterface $entity */ + $entity = DeveloperApp::create([ + 'appId' => 1, + 'name' => $this->randomMachineName(), + 'status' => App::STATUS_APPROVED, + 'displayName' => $this->randomMachineName(), + ]); + $entity->setOwner($this->account); + $this->queueDeveloperAppResponse($entity); + $entity->save(); + + return $entity; + } + + /** + * Helper to create a Team entity. + * + * @return \Drupal\apigee_edge_teams\Entity\TeamInterface + * A Team entity. + * + * @throws \Drupal\Core\Entity\EntityStorageException + */ + protected function createTeam(): TeamInterface { + /** @var \Drupal\apigee_edge_teams\Entity\TeamInterface $team */ + $team = Team::create([ + 'name' => $this->randomMachineName(), + 'displayName' => $this->randomGenerator->name(), + ]); + $this->queueCompanyResponse($team->decorated()); + $this->queueDeveloperResponse($this->account); + $team->save(); + + return $team; + } + + /** + * Helper to add Edge entity response to stack. * * @param \Drupal\apigee_edge\Entity\DeveloperAppInterface $app * The app.