Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions API.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,14 @@ public function getAlerts($idSites, $ifSuperUserReturnAllAlerts = false)
* @param bool|string $reportValue
* @param array $reportMediums
* @param string $slackChannelID
* @param string $msTeamsWebhookUrl
* @return int ID of new Alert
*/
public function addAlert($name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition = false, $reportValue = false, array $reportMediums = [], string $slackChannelID = '')
public function addAlert($name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition = false, $reportValue = false, array $reportMediums = [], string $slackChannelID = '', string $msTeamsWebhookUrl = '')
{
$idSites = Site::getIdSitesFromIdSitesString($idSites);

$this->checkAlert($idSites, $name, $period, $emailMe, $additionalEmails, $phoneNumbers, $slackChannelID, $metricCondition, $metric, $comparedTo, $reportCondition, $reportUniqueId, $reportMediums);
$this->checkAlert($idSites, $name, $period, $emailMe, $additionalEmails, $phoneNumbers, $slackChannelID, $msTeamsWebhookUrl, $metricCondition, $metric, $comparedTo, $reportCondition, $reportUniqueId, $reportMediums);

$name = Common::unsanitizeInputValue($name);
$login = Piwik::getCurrentUserLogin();
Expand All @@ -148,7 +149,7 @@ public function addAlert($name, $idSites, $period, $emailMe, $additionalEmails,

$metricValue = Common::forceDotAsSeparatorForDecimalPoint((float)$metricValue);

return $this->getModel()->createAlert($name, $idSites, $login, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID);
return $this->getModel()->createAlert($name, $idSites, $login, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID, $msTeamsWebhookUrl);
}

private function filterAdditionalEmails($additionalEmails)
Expand Down Expand Up @@ -182,13 +183,14 @@ private function filterPhoneNumbers($phoneNumbers)
return array_values($phoneNumbers);
}

private function checkAlert($idSites, $name, $period, &$emailMe, &$additionalEmails, &$phoneNumbers, &$slackChannelID, $metricCondition, $metricValue, $comparedTo, $reportCondition, $reportUniqueId, $reportMediums)
private function checkAlert($idSites, $name, $period, &$emailMe, &$additionalEmails, &$phoneNumbers, &$slackChannelID, &$msTeamsWebhookUrl, $metricCondition, $metricValue, $comparedTo, $reportCondition, $reportUniqueId, $reportMediums)
{
Piwik::checkUserHasViewAccess($idSites);
$additionalEmails = in_array('email', $reportMediums) ? $this->filterAdditionalEmails($additionalEmails) : [];
$phoneNumbers = in_array('mobile', $reportMediums) ? $this->filterPhoneNumbers($phoneNumbers) : [];
$emailMe = in_array('email', $reportMediums) && $emailMe;
$slackChannelID = in_array('slack', $reportMediums) ? $slackChannelID : '';
$msTeamsWebhookUrl = in_array('teams', $reportMediums) ? $msTeamsWebhookUrl : '';

$this->validator->checkName($name);
$this->validator->checkPeriod($period);
Expand Down Expand Up @@ -227,17 +229,18 @@ private function checkAlert($idSites, $name, $period, &$emailMe, &$additionalEma
* @param bool|string $reportValue
* @param array $reportMediums
* @param string $slackChannelID
* @param string $msTeamsWebhookUrl
*
* @return boolean
*/
public function editAlert($idAlert, $name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition = false, $reportValue = false, array $reportMediums = [], string $slackChannelID = '')
public function editAlert($idAlert, $name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition = false, $reportValue = false, array $reportMediums = [], string $slackChannelID = '', string $msTeamsWebhookUrl = '')
{
// make sure alert exists and user has permission to read
$this->getAlert($idAlert);

$idSites = Site::getIdSitesFromIdSitesString($idSites);

$this->checkAlert($idSites, $name, $period, $emailMe, $additionalEmails, $phoneNumbers, $slackChannelID, $metricCondition, $metric, $comparedTo, $reportCondition, $reportUniqueId, $reportMediums);
$this->checkAlert($idSites, $name, $period, $emailMe, $additionalEmails, $phoneNumbers, $slackChannelID, $msTeamsWebhookUrl, $metricCondition, $metric, $comparedTo, $reportCondition, $reportUniqueId, $reportMediums);

$name = Common::unsanitizeInputValue($name);

Expand All @@ -248,7 +251,7 @@ public function editAlert($idAlert, $name, $idSites, $period, $emailMe, $additio

$metricValue = Common::forceDotAsSeparatorForDecimalPoint((float)$metricValue);

return $this->getModel()->updateAlert($idAlert, $name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID);
return $this->getModel()->updateAlert($idAlert, $name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID, $msTeamsWebhookUrl);
}

/**
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Changelog

* 5.2.0 - 2025-11-24 - Added ability to send alerts to a Microsoft Teams channel
* 5.1.0 - 2025-09-29 Refactored UI to support different mediums to alert and changes to alert via Slack
* 5.0.7 - 2025-07-07 Textual changes
* 5.0.6 - 2024-09-23 Added check if reports used by alert are done archiving and retry if not archived
Expand Down
4 changes: 3 additions & 1 deletion CustomAlerts.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ public function removePhoneNumberFromAllAlerts($phoneNumber)
$alert['report_condition'],
$alert['report_matched'],
$alert['report_mediums'],
$alert['slack_channel_id']
$alert['slack_channel_id'],
$alert['ms_teams_webhook_url']
);
}
}
Expand Down Expand Up @@ -276,6 +277,7 @@ public static function getReportMediumOptions(): array
['key' => 'email', 'value' => Piwik::translate('CustomAlerts_MediumEmail'), 'disabled' => false],
['key' => 'mobile', 'value' => Piwik::translate('CustomAlerts_MediumMobile'), 'disabled' => !PluginManager::getInstance()->isPluginActivated('MobileMessaging')],
['key' => 'slack', 'value' => Piwik::translate('CustomAlerts_MediumSlack'), 'disabled' => !PluginManager::getInstance()->isPluginActivated('Slack')],
['key' => 'teams', 'value' => Piwik::translate('CustomAlerts_MediumMicrosoftTeams'), 'disabled' => !PluginManager::getInstance()->isPluginActivated('MicrosoftTeams')],
];
}

Expand Down
18 changes: 12 additions & 6 deletions Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public static function install()
`email_me` BOOLEAN NOT NULL DEFAULT '0',
`additional_emails` TEXT ,
`phone_numbers` TEXT ,
`slack_channel_id` VARCHAR(50) NULL ";
`slack_channel_id` VARCHAR(50) NULL ,
`ms_teams_webhook_url` VARCHAR(500) NULL ";

DbHelper::createTable('alert', $tableAlert);

Expand Down Expand Up @@ -69,6 +70,7 @@ public static function install()
`additional_emails` TEXT ,
`phone_numbers` TEXT ,
`slack_channel_id` VARCHAR(50) NULL ,
`ms_teams_webhook_url` VARCHAR(500) NULL ,
PRIMARY KEY (idtriggered)";

DbHelper::createTable('alert_triggered', $tableAlertLog);
Expand Down Expand Up @@ -254,11 +256,12 @@ public function getAllAlertsForPeriod($period)
* @param string $reportValue
* @param array $reportMediums
* @param string $slackChannelID
* @param string $msTeamsWebhookUrl
*
* @return int ID of new Alert
* @throws \Exception
*/
public function createAlert($name, $idSites, $login, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID)
public function createAlert($name, $idSites, $login, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID, $msTeamsWebhookUrl)
{
$idAlert = $this->getNextAlertId();

Expand All @@ -278,7 +281,8 @@ public function createAlert($name, $idSites, $login, $period, $emailMe, $additio
'report_condition' => $reportCondition,
'report_matched' => $reportValue,
'report_mediums' => json_encode($reportMediums),
'slack_channel_id' => $slackChannelID
'slack_channel_id' => $slackChannelID,
'ms_teams_webhook_url' => $msTeamsWebhookUrl,
);

$db = $this->getDb();
Expand Down Expand Up @@ -337,11 +341,12 @@ private function removeAllSites($idAlert)
* @param string $reportValue
* @param array $reportMediums
* @param string $slackChannelID
* @param string $msTeamsWebhookUrl
*
* @return int
* @throws \Exception
*/
public function updateAlert($idAlert, $name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID)
public function updateAlert($idAlert, $name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID, $msTeamsWebhookUrl)
{
$alert = array(
'name' => $name,
Expand All @@ -357,7 +362,8 @@ public function updateAlert($idAlert, $name, $idSites, $period, $emailMe, $addit
'report_condition' => $reportCondition,
'report_matched' => $reportValue,
'report_mediums' => json_encode($reportMediums),
'slack_channel_id' => $slackChannelID
'slack_channel_id' => $slackChannelID,
'ms_teams_webhook_url' => $msTeamsWebhookUrl,
);

$db = $this->getDb();
Expand Down Expand Up @@ -387,7 +393,7 @@ public function triggerAlert($idAlert, $idSite, $valueNew, $valueOld, $datetime)
{
$alert = $this->getAlert($idAlert);

$keysToKeep = array('idalert', 'name', 'login', 'period', 'metric', 'metric_condition', 'metric_matched', 'report', 'report_condition', 'report_matched', 'report_mediums', 'compared_to', 'email_me', 'additional_emails', 'phone_numbers', 'slack_channel_id');
$keysToKeep = array('idalert', 'name', 'login', 'period', 'metric', 'metric_condition', 'metric_matched', 'report', 'report_condition', 'report_matched', 'report_mediums', 'compared_to', 'email_me', 'additional_emails', 'phone_numbers', 'slack_channel_id', 'ms_teams_webhook_url');

$triggeredAlert = array();
foreach ($keysToKeep as $key) {
Expand Down
47 changes: 47 additions & 0 deletions Updates/5.2.0.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nitpick, don't fix. Use strict.

namespace Piwik\Plugins\CustomAlerts;

use Piwik\Common;
use Piwik\Updater;
use Piwik\Updater\Migration\Factory as MigrationFactory;
use Piwik\Updates;

/**
*/
class Updates_5_2_0 extends Updates
{
/**
* @var MigrationFactory
*/
private $migration;

public function __construct(MigrationFactory $factory)
{
$this->migration = $factory;
}

public function doUpdate(Updater $updater)
{
$updater->executeMigrations(__FILE__, $this->getMigrations($updater));
}

public function getMigrations(Updater $updater)
{
$alertTableName = Common::prefixTable('alert');
$alertTriggeredTableName = Common::prefixTable('alert_triggered');

return array(
$this->migration->db->addColumn('alert', 'ms_teams_webhook_url', 'VARCHAR(500) NULL', 'slack_channel_id'),
$this->migration->db->addColumn('alert_triggered', 'ms_teams_webhook_url', 'VARCHAR(500) NULL', 'slack_channel_id'),
);
Comment on lines +42 to +45
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I believe code can be migrated before db is updated, for up to an hour. Codex impact assessment below.

I'm not sure what our policy for this stuff is currently - I suspect previously we wouldn't have thought about it, and wouldn't have seen any of the errors.

Maybe a good middle ground is to is to fix Model.php only, so that alerts continue working until the schema is updated. A fix would be to check for existence of the column first.

  - Updates/5.2.0.php:42-45 is what adds ms_teams_webhook_url to both alert and alert_triggered. If that migration isn’t run, those columns simply don’t exist.
  - Model.php:264-289 and Model.php:349-371 always include ms_teams_webhook_url in the insert/update payload. Without the column, every call to the addAlert/editAlert API fails with an
    “Unknown column” SQL error, so no alert can be saved (not just Teams alerts).
  - When an alert fires, Model.php:392-417 copies the same field into the alert_triggered insert. With the old schema this insert also errors, so the scheduler can’t persist triggered
    alerts and all downstream notifications (email/SMS/Slack) abort for that run.
  - Housekeeping code such as CustomAlerts.php:166-183 reads $alert['ms_teams_webhook_url'] while updating alerts. If the column is missing, those arrays never have that key, leading to
    PHP notices and preventing clean saves even when editing unrelated fields.
  - Because the UI and API contract now expose a ms_teams_webhook_url property (see vue/src/EditAlert/EditAlert.vue:114-118 and API.php:136-254), users will see the Teams options but
    any submission will error until the schema is updated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I believe this is okay to pass, as we have done similar thing in past and have received very few complaints

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Only add//update will be broken, till the time we run the update, existing alerts will keep working.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You are right - triggerAlert could potentially insert the wrong column, but it's reading from the db in the first place, so never will.

}
}
1 change: 1 addition & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"MediumEmail": "Email",
"MediumMobile": "Mobile",
"MediumSlack": "Slack",
"MediumMicrosoftTeams": "Teams",
"MediumTitle": "Send alerts via",
"MediumDescription": "Choose how you want to receive alerts when this custom alert is triggered.",
"EmptyReportMediums": "At least one delivery method must be selected.",
Expand Down
2 changes: 1 addition & 1 deletion phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

<rule ref="Generic.Files.LineLength">
<properties>
<property name="lineLimit" value="300" />
<property name="lineLimit" value="350" />
</properties>
<exclude-pattern>tests/*</exclude-pattern>
</rule>
Expand Down
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "CustomAlerts",
"description": "Create custom Alerts to be notified of important changes on your website or app! ",
"version": "5.1.0",
"version": "5.2.0",
"require": {
"matomo": ">=5.0.0-b1,<6.0.0-b1"
},
Expand Down
2 changes: 1 addition & 1 deletion tests/Fixtures/CustomAlerts.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ private function createAlert($name, $period, $idSites, $metric, $report, $login
}

$model = new Model();
$model->createAlert($name, $idSites, $login, $period, 0, $emails, $phoneNumbers, $metric, 'less_than', 5, $comparedTo = 1, $report, 'matches_exactly', $reportMatched, ['email', 'mobile'], '');
$model->createAlert($name, $idSites, $login, $period, 0, $emails, $phoneNumbers, $metric, 'less_than', 5, $comparedTo = 1, $report, 'matches_exactly', $reportMatched, ['email', 'mobile'], '', '');
}

private function triggerAlert($idAlert, $valueNew, $valueOld, $datetime)
Expand Down
3 changes: 3 additions & 0 deletions tests/Integration/ApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ protected function createAlert(
$reportCondition,
'Piwik',
['email', 'mobile'],
'',
''
);
return $id;
Expand Down Expand Up @@ -227,6 +228,7 @@ protected function assertIsAlert(
'additional_emails' => array('test1@example.com', 'test2@example.com'),
'phone_numbers' => array(),
'slack_channel_id' => '',
'ms_teams_webhook_url' => '',
'compared_to' => 1,
'id_sites' => $idSites,
'report_mediums' => ['email', 'mobile']
Expand Down Expand Up @@ -583,6 +585,7 @@ public function test_triggerAlert_getTriggeredAlertsForPeriod_ShouldMarkAlertAsT
'additional_emails' => array('test1@example.com', 'test2@example.com'),
'phone_numbers' => array(),
'slack_channel_id' => '',
'ms_teams_webhook_url' => '',
'email_me' => 0,
'compared_to' => 1,
'id_sites' => array(1, 2),
Expand Down
3 changes: 3 additions & 0 deletions tests/Integration/CustomAlertsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ private function createAlert($name, $phoneNumbers, $idSites = array(1), $login =
'matches_exactly',
'Piwik',
$mediums,
'',
''
);

Expand Down Expand Up @@ -277,6 +278,7 @@ public function testGetReportMediumOptions()
['key' => 'email', 'value' => 'CustomAlerts_MediumEmail', 'disabled' => false],
['key' => 'mobile', 'value' => 'CustomAlerts_MediumMobile', 'disabled' => false],
['key' => 'slack', 'value' => 'CustomAlerts_MediumSlack', 'disabled' => true],
['key' => 'teams', 'value' => 'CustomAlerts_MediumMicrosoftTeams', 'disabled' => true],
], CustomAlerts::getReportMediumOptions());
}

Expand All @@ -287,6 +289,7 @@ public function testGetReportMediumOptionsWhenMobileMessagingPluginDisabled()
['key' => 'email', 'value' => 'CustomAlerts_MediumEmail', 'disabled' => false],
['key' => 'mobile', 'value' => 'CustomAlerts_MediumMobile', 'disabled' => true],
['key' => 'slack', 'value' => 'CustomAlerts_MediumSlack', 'disabled' => true],
['key' => 'teams', 'value' => 'CustomAlerts_MediumMicrosoftTeams', 'disabled' => true],
], CustomAlerts::getReportMediumOptions());
}
}
9 changes: 6 additions & 3 deletions tests/Integration/ModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private function createAlert(
$emails = array('test1@example.com', 'test2@example.com');
$phoneNumbers = array('0123456789');

$id = $this->model->createAlert($name, $idSites, $login, $period, 0, $emails, $phoneNumbers, $metric, 'less_than', 5, $comparedTo = 1, $report, 'matches_exactly', 'Piwik', ['email', 'mobile'], '');
$id = $this->model->createAlert($name, $idSites, $login, $period, 0, $emails, $phoneNumbers, $metric, 'less_than', 5, $comparedTo = 1, $report, 'matches_exactly', 'Piwik', ['email', 'mobile'], '', '');
return $id;
}

Expand All @@ -58,13 +58,13 @@ public function test_install_ShouldNotFailAndActuallyCreateTheDatabases()
$this->assertContainTables(array('alert', 'alert_site', 'alert_triggered'));

$columns = Db::fetchAll('show columns from ' . Common::prefixTable('alert'));
$this->assertCount(16, $columns);
$this->assertCount(17, $columns);

$columns = Db::fetchAll('show columns from ' . Common::prefixTable('alert_site'));
$this->assertCount(2, $columns);

$columns = Db::fetchAll('show columns from ' . Common::prefixTable('alert_triggered'));
$this->assertCount(22, $columns);
$this->assertCount(23, $columns);
}

private function assertContainTables($expectedTables)
Expand Down Expand Up @@ -148,6 +148,7 @@ private function assertIsAlert(
'additional_emails' => array('test1@example.com', 'test2@example.com'),
'phone_numbers' => array('0123456789'),
'slack_channel_id' => '',
'ms_teams_webhook_url' => '',
'compared_to' => 1,
'id_sites' => $idSites,
'report_mediums' => ['email', 'mobile'],
Expand Down Expand Up @@ -205,6 +206,7 @@ private function editAlert(
'matches_exactly',
'Piwik',
['email', 'mobile'],
'',
''
);
return $id;
Expand Down Expand Up @@ -324,6 +326,7 @@ public function test_triggerAlert_getTriggeredAlertsForPeriod_ShouldMarkAlertAsT
'additional_emails' => array('test1@example.com', 'test2@example.com'),
'phone_numbers' => array('0123456789'),
'slack_channel_id' => '',
'ms_teams_webhook_url' => '',
'email_me' => 0,
'id_sites' => array(1, 2),
'report_mediums' => ['email', 'mobile']
Expand Down
2 changes: 1 addition & 1 deletion tests/Integration/ProcessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ private function createAlert(
$emails = ['test1@example.com', 'test2@example.com'];
$phoneNumbers = ['0123456789'];

return $this->alertModel->createAlert($name, $idSites, $login, $period, 0, $emails, $phoneNumbers, $metric, 'less_than', 5, $comparedTo = 1, $report, 'matches_exactly', 'Piwik', ['email', 'mobile'], '');
return $this->alertModel->createAlert($name, $idSites, $login, $period, 0, $emails, $phoneNumbers, $metric, 'less_than', 5, $comparedTo = 1, $report, 'matches_exactly', 'Piwik', ['email', 'mobile'], '', '');
}

private function getTestTask(): Task
Expand Down
Binary file modified tests/UI/expected-ui-screenshots/CustomAlerts_report_mediums.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading