mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-21 17:48:43 +02:00
N°2039 N°7298 - Allow Actions to be asynchronous (#625)
* N°2039 - Allows Actions to be asynchronous * Add unit test * Apply suggestions from code review * Apply suggestions from code review Co-authored-by: Molkobain <lajarige.guillaume@free.fr> * Apply suggestions from code review Co-authored-by: Molkobain <lajarige.guillaume@free.fr> * Apply suggestions from code review Co-authored-by: Molkobain <lajarige.guillaume@free.fr> * Apply suggestions from code review Co-authored-by: Molkobain <lajarige.guillaume@free.fr> * Apply suggestions from code review Co-authored-by: Molkobain <lajarige.guillaume@free.fr> * Apply suggestions from code review Co-authored-by: Molkobain <lajarige.guillaume@free.fr> * Apply suggestions from code review Co-authored-by: Molkobain <lajarige.guillaume@free.fr> * Apply suggestions from code review Co-authored-by: Molkobain <lajarige.guillaume@free.fr> * Apply suggestions from code review Co-authored-by: Molkobain <lajarige.guillaume@free.fr> * Apply suggestions from code review * GetAsynchronousGlobalSetting is now static * None of the async task for itop notification attribute should be null * Rename follow_global_setting to user_global_setting --------- Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
This commit is contained in:
@@ -81,6 +81,7 @@ abstract class Action extends cmdbAbstractObject
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("trigger_list",
|
||||
array("linked_class" => "lnkTriggerAction", "ext_key_to_me" => "action_id", "ext_key_to_remote" => "trigger_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => array(), "display_style" => 'property')));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("asynchronous", array("allowed_values" => new ValueSetEnum(['use_global_setting' => 'Use global settings','yes' => 'Yes' ,'no' => 'No']), "sql" => "asynchronous", "default_value" => 'use_global_setting', "is_null_allowed" => false, "depends_on" => array())));
|
||||
|
||||
// Display lists
|
||||
// - Attributes to be displayed for the complete details
|
||||
@@ -196,7 +197,20 @@ abstract class Action extends cmdbAbstractObject
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidConfigParamException
|
||||
* @param \Combodo\iTop\Application\WebPage\WebPage $oPage
|
||||
*
|
||||
* @throws \ApplicationException
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \DictExceptionMissingString
|
||||
* @throws \InvalidConfigParamException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
* @throws \OQLException
|
||||
* @throws \ReflectionException
|
||||
* @since 3.2.0 N°5472 method creation
|
||||
*/
|
||||
public function GetLastExecutionsTabContent(WebPage $oPage): void
|
||||
@@ -227,6 +241,32 @@ abstract class Action extends cmdbAbstractObject
|
||||
|
||||
$oPage->AddUiBlock($oExecutionsListBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be overloaded by the children classes to return the value of their global asynchronous setting (eg. `email_asynchronous` for `\ActionEmail`, `prefer_asynchronous` for `\ActionWebhook`, ...)
|
||||
*
|
||||
* @return bool true if the global setting for this kind of action if to be executed asynchronously, false otherwise.
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public static function GetAsynchronousGlobalSetting(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool true if that action instance should be executed asynchronously, otherwise false
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function IsAsynchronous(): bool
|
||||
{
|
||||
$sAsynchronous = $this->Get('asynchronous');
|
||||
if ($sAsynchronous === 'use_global_setting') {
|
||||
return static::GetAsynchronousGlobalSetting();
|
||||
}
|
||||
return $sAsynchronous === 'yes';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,6 +421,7 @@ class ActionEmail extends ActionNotification
|
||||
),
|
||||
'fieldset:ActionEmail:trigger' => array(
|
||||
0 => 'trigger_list',
|
||||
1 => 'asynchronous'
|
||||
),
|
||||
),
|
||||
'col:col2' => array(
|
||||
@@ -616,7 +657,7 @@ class ActionEmail extends ActionNotification
|
||||
else
|
||||
{
|
||||
$aErrors = [];
|
||||
$iRes = $oEmail->Send($aErrors, false, $oLog); // allow asynchronous mode
|
||||
$iRes = $oEmail->Send($aErrors, $this->IsAsynchronous() ? Email::ENUM_SEND_FORCE_ASYNCHRONOUS : Email::ENUM_SEND_FORCE_SYNCHRONOUS, $oLog);
|
||||
switch ($iRes)
|
||||
{
|
||||
case EMAIL_SEND_OK:
|
||||
@@ -877,4 +918,13 @@ class ActionEmail extends ActionNotification
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public static function GetAsynchronousGlobalSetting(): bool
|
||||
{
|
||||
return utils::GetConfig()->Get('email_asynchronous');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
use Combodo\iTop\Service\Notification\Event\EventiTopNotificationService;
|
||||
|
||||
|
||||
/**
|
||||
@@ -457,3 +458,87 @@ class AsyncSendEmail extends AsyncTask
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An async notification to be sent to iTop users
|
||||
* @since 3.2.0
|
||||
*/
|
||||
class AsyncSendiTopNotifications extends AsyncTask {
|
||||
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "core/cmdb",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "created",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array(),
|
||||
"db_table" => "priv_async_send_itop_notifications",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeText("recipients", array("allowed_values"=>null, "sql"=>"recipients", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalKey("action_id", array("targetclass"=>"Action", "allowed_values"=>null, "sql"=>"action_id", "default_value"=>null, "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalKey("trigger_id", array("targetclass"=>"Trigger", "allowed_values"=>null, "sql"=>"trigger_id", "default_value"=>null, "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeText("title", array("allowed_values"=>null, "sql"=>"title", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeText("message", array("allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeInteger("object_id", array("allowed_values"=>null, "sql"=>"object_id", "default_value"=>null, "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeString("object_class", array("allowed_values"=>null, "sql"=>"object_class", "default_value"=>null, "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeText("url", array("allowed_values"=>null, "sql"=>"url", "default_value"=>null, "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
|
||||
MetaModel::Init_AddAttribute(new AttributeDateTime("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>null, "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public static function AddToQueue(int $iActionId, int $iTriggerId, array $aRecipients, string $sMessage, string $sTitle, string $sUrl, int $iObjectId, ?string $sObjectClass): void
|
||||
{
|
||||
$oNew = new static();
|
||||
$oNew->Set('action_id', $iActionId);
|
||||
$oNew->Set('trigger_id', $iTriggerId);
|
||||
$oNew->Set('recipients', json_encode($aRecipients));
|
||||
$oNew->Set('message', $sMessage);
|
||||
$oNew->Set('title', $sTitle);
|
||||
$oNew->Set('url', $sUrl);
|
||||
$oNew->Set('object_id', $iObjectId);
|
||||
$oNew->Set('object_class', $sObjectClass);
|
||||
$oNew->SetCurrentDate('date');
|
||||
|
||||
$oNew->DBInsert();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function DoProcess()
|
||||
{
|
||||
$oAction = MetaModel::GetObject('Action', $this->Get('action_id'));
|
||||
$iTriggerId = $this->Get('trigger_id');
|
||||
$aRecipients = json_decode($this->Get('recipients'));
|
||||
$sMessage = $this->Get('message');
|
||||
$sTitle = $this->Get('title');
|
||||
$sUrl = $this->Get('url');
|
||||
$iObjectId = $this->Get('object_id');
|
||||
$sObjectClass = $this->Get('object_class');
|
||||
$sDate = $this->Get('date');
|
||||
|
||||
foreach ($aRecipients as $iRecipientId)
|
||||
{
|
||||
$oEvent = EventiTopNotificationService::MakeEventFromAction($oAction, $iRecipientId, $iTriggerId, $sMessage, $sTitle, $sUrl, $iObjectId, $sObjectClass, $sDate);
|
||||
$oEvent->DBInsertNoReload();
|
||||
}
|
||||
|
||||
return "Sent";
|
||||
}
|
||||
}
|
||||
@@ -1619,6 +1619,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'notifications.itop.send_asynchronously' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true then iTop notifications will be sent asynchronously',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'notifications.itop.newsroom_cache_time' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Duration in min between each fetch for notifications in newsroom',
|
||||
|
||||
@@ -193,6 +193,9 @@
|
||||
<item id="trigger_list">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="asynchronous">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</item>
|
||||
</items>
|
||||
@@ -258,7 +261,23 @@
|
||||
$oRecipientsSearch = DBObjectSearch::FromOQL($this->Get('recipients'));
|
||||
$oRecipientsSearch->AllowAllData();
|
||||
$oRecipientsSet = new DBObjectSet($oRecipientsSearch);
|
||||
$bIsAsync = $this->IsAsynchronous();
|
||||
[$sPreviousLanguage, $aPreviousPluginProperties] = $this->SetNotificationLanguage();
|
||||
|
||||
if($bIsAsync === true){
|
||||
$aRecipients = [];
|
||||
}
|
||||
|
||||
$sMessage = MetaModel::ApplyParams($this->Get('message'), $aContextArgs);
|
||||
$sTitle = MetaModel::ApplyParams($this->Get('title'), $aContextArgs);
|
||||
$sUrl = MetaModel::ApplyParams($this->Get('url'), $aContextArgs);
|
||||
$iObjectId = 0;
|
||||
$sObjectClass = null;
|
||||
if (array_key_exists('this->object()', $aContextArgs)) {
|
||||
$iObjectId = $aContextArgs['this->object()']->GetKey();
|
||||
$sObjectClass = get_class($aContextArgs['this->object()']);
|
||||
}
|
||||
|
||||
while ($oRecipient = $oRecipientsSet->Fetch()) {
|
||||
// Skip recipients that have no users
|
||||
if (get_class($oRecipient) === Person::class && UserRights::GetUserFromPerson($oRecipient) === null) {
|
||||
@@ -267,45 +286,32 @@
|
||||
if (!\Combodo\iTop\Service\Notification\NotificationsService::GetInstance()->IsSubscribed($oTrigger, $this, $oRecipient)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (array_key_exists('this->object()', $aContextArgs)) {
|
||||
$iObjectId = $aContextArgs['this->object()']->GetKey();
|
||||
$sObjectClass = get_class($aContextArgs['this->object()']);
|
||||
} else {
|
||||
$iObjectId = 0;
|
||||
$sObjectClass = null;
|
||||
}
|
||||
|
||||
$oEvent = new EventiTopNotification();
|
||||
$oEvent->Set('title', MetaModel::ApplyParams($this->Get('title'), $aContextArgs));
|
||||
$oEvent->Set('message', MetaModel::ApplyParams($this->Get('message'), $aContextArgs));
|
||||
// Compute icon
|
||||
// - First check if one is defined on the action
|
||||
if (false === $this->Get('icon')->IsEmpty()) {
|
||||
$oIcon = $this->Get('icon');
|
||||
}
|
||||
// - Then, check if the action is for a DM object and if its class has an icon
|
||||
elseif ($iObjectId > 0 && utils::IsNotNullOrEmptyString(MetaModel::GetClassIcon($sObjectClass, false))) {
|
||||
$oIcon = MetaModel::GetAttributeDef(EventiTopNotification::class, 'icon')->MakeRealValue(MetaModel::GetClassIcon($sObjectClass, false), $oEvent);
|
||||
}
|
||||
// - Otherwise, fallback on the compact logo of the application
|
||||
else {
|
||||
$oIcon = MetaModel::GetAttributeDef(EventiTopNotification::class, 'icon')->MakeRealValue(\Combodo\iTop\Application\Branding::GetCompactMainLogoAbsoluteUrl(), $oEvent);
|
||||
}
|
||||
$oEvent->Set('icon', $oIcon);
|
||||
$oEvent->Set('priority', $this->Get('priority'));
|
||||
$oEvent->Set('contact_id', $oRecipient->GetKey());
|
||||
$oEvent->Set('trigger_id', $oTrigger->GetKey());
|
||||
$oEvent->Set('action_id', $this->GetKey());
|
||||
$iObjectId = array_key_exists('this->object()', $aContextArgs) ? $aContextArgs['this->object()']->GetKey() : 0;
|
||||
$oEvent->Set('object_id', $iObjectId);
|
||||
$oEvent->Set('url', MetaModel::ApplyParams($this->Get('url'), $aContextArgs));
|
||||
$oEvent->DBInsertNoReload();
|
||||
|
||||
if($bIsAsync === true) {
|
||||
$aRecipients[] = $oRecipient->GetKey();
|
||||
} else {
|
||||
$oEvent = Combodo\iTop\Service\Notification\Event\EventiTopNotificationService::MakeEventFromAction($this, $oRecipient->GetKey(), $oTrigger->GetKey(), $sMessage, $sTitle, $sUrl, $iObjectId, $sObjectClass);
|
||||
$oEvent->DBInsertNoReload();
|
||||
}
|
||||
|
||||
\Combodo\iTop\Service\Notification\NotificationsService::GetInstance()->RegisterSubscription($oTrigger, $this, $oRecipient);
|
||||
}
|
||||
if ($bIsAsync === true) {
|
||||
AsyncSendiTopNotifications::AddToQueue($this->GetKey(), $oTrigger->GetKey(), $aRecipients, $sMessage, $sTitle, $sUrl, $iObjectId, $sObjectClass);
|
||||
}
|
||||
$this->SetNotificationLanguage($sPreviousLanguage, $aPreviousPluginProperties['language_code'] ?? null);
|
||||
}
|
||||
]]></code>
|
||||
</method>
|
||||
<method id="GetAsynchronousGlobalSetting">
|
||||
<comment></comment>
|
||||
<static>true</static>
|
||||
<access>public</access>
|
||||
<code><![CDATA[
|
||||
public static function GetAsynchronousGlobalSetting(): bool
|
||||
{
|
||||
return utils::GetConfig()->Get('notifications.itop.send_asynchronously');
|
||||
}
|
||||
]]></code>
|
||||
</method>
|
||||
</methods>
|
||||
|
||||
Reference in New Issue
Block a user