diff --git a/plugins/CoreVue/types/index.d.ts b/plugins/CoreVue/types/index.d.ts
index 2097eaa8777..40fefde3ded 100644
--- a/plugins/CoreVue/types/index.d.ts
+++ b/plugins/CoreVue/types/index.d.ts
@@ -91,6 +91,8 @@ declare global {
sendContentAsDownload(filename: string, content: any, mimeType?: string): void;
showVisitorProfilePopup(visitorId: string, idSite: string|number): void;
hideAjaxError(): void;
+ showAjaxLoading(loadingDivID?: string): void;
+ hideAjaxLoading(loadingDivID?: string): void;
refreshAfter(timeoutPeriod: number): void;
}
diff --git a/plugins/ProfessionalServices/API.php b/plugins/ProfessionalServices/API.php
new file mode 100644
index 00000000000..5abbb47bb18
--- /dev/null
+++ b/plugins/ProfessionalServices/API.php
@@ -0,0 +1,49 @@
+promoWidgetDismissal = $promoWidgetDismissal;
+ }
+
+ /**
+ * Dismisses a promo widget to no longer be shown in the menu
+ *
+ * @internal
+ *
+ * @param string $widgetName
+ * @return bool
+ * @throws \Piwik\NoAccessException
+ */
+ public function dismissWidget(string $widgetName): bool
+ {
+ Piwik::checkUserIsNotAnonymous();
+
+ if (!DismissibleWidget::exists($widgetName)) {
+ throw new \Exception('Can\'t dismiss unknown widget ' . $widgetName);
+ }
+
+ $this->promoWidgetDismissal->dismissPromoWidget($widgetName);
+
+ return true;
+ }
+}
diff --git a/plugins/ProfessionalServices/ProfessionalServices.php b/plugins/ProfessionalServices/ProfessionalServices.php
index 53fa459328a..90e18b4d2c2 100644
--- a/plugins/ProfessionalServices/ProfessionalServices.php
+++ b/plugins/ProfessionalServices/ProfessionalServices.php
@@ -34,6 +34,7 @@ public function registerEvents()
'Template.afterVisitorProfileOverview' => 'getSessionRecordingPromo',
'Template.afterPagePerformanceReport' => 'getSeoWebVitalsPromo',
'Template.afterSearchEngines' => 'getSeoWebVitalsPromo',
+ 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
);
}
@@ -43,6 +44,19 @@ public function getStylesheetFiles(&$stylesheets)
$stylesheets[] = 'plugins/ProfessionalServices/stylesheets/widget.less';
}
+ public function getClientSideTranslationKeys(&$translationKeys)
+ {
+ $translationKeys[] = 'ProfessionalServices_DismissedNotification';
+ $translationKeys[] = 'ProfessionalServices_PromoFunnels';
+ $translationKeys[] = 'ProfessionalServices_PromoFormAnalytics';
+ $translationKeys[] = 'ProfessionalServices_PromoMediaAnalytics';
+ $translationKeys[] = 'ProfessionalServices_PromoAbTesting';
+ $translationKeys[] = 'ProfessionalServices_PromoHeatmaps';
+ $translationKeys[] = 'ProfessionalServices_PromoSessionRecording';
+ $translationKeys[] = 'ProfessionalServices_PromoCustomReports';
+ $translationKeys[] = 'ProfessionalServices_PromoCrashAnalytics';
+ }
+
public function isRequestForDashboardWidget()
{
$isWidget = Common::getRequestVar('widget', 0, 'int');
@@ -157,5 +171,4 @@ public function getSeoWebVitalsPromo(&$out)
$out .= $view->render();
}
}
-
}
diff --git a/plugins/ProfessionalServices/PromoWidgetApplicable.php b/plugins/ProfessionalServices/PromoWidgetApplicable.php
index ba4e945c9b1..0fe45495f7a 100644
--- a/plugins/ProfessionalServices/PromoWidgetApplicable.php
+++ b/plugins/ProfessionalServices/PromoWidgetApplicable.php
@@ -24,13 +24,19 @@ class PromoWidgetApplicable
*/
private $config;
- public function __construct(Manager $manager, Config $config)
+ /**
+ * @var PromoWidgetDismissal
+ */
+ private $promoWidgetDismissal;
+
+ public function __construct(Manager $manager, Config $config, PromoWidgetDismissal $promoWidgetDismissal)
{
$this->manager = $manager;
$this->config = $config;
+ $this->promoWidgetDismissal = $promoWidgetDismissal;
}
- public function check(string $pluginName): bool
+ public function check(string $pluginName, string $widgetName): bool
{
if (Advertising::isAdsEnabledInConfig($this->config->General) === false) {
return false;
@@ -44,6 +50,10 @@ public function check(string $pluginName): bool
return false;
}
+ if ($this->promoWidgetDismissal->isPromoWidgetDismissedForCurrentUser($widgetName)) {
+ return false;
+ }
+
return $this->manager->isPluginActivated($pluginName) === false;
}
}
diff --git a/plugins/ProfessionalServices/PromoWidgetDismissal.php b/plugins/ProfessionalServices/PromoWidgetDismissal.php
new file mode 100644
index 00000000000..9b11832a580
--- /dev/null
+++ b/plugins/ProfessionalServices/PromoWidgetDismissal.php
@@ -0,0 +1,44 @@
+getDismissedWidgetOptionName($widgetName), time());
+ }
+
+ public function isPromoWidgetDismissedForCurrentUser(string $widgetName): bool
+ {
+ $isAnonUser = Piwik::isUserIsAnonymous();
+
+ if ($isAnonUser) {
+ return false;
+ }
+
+ return $this->isPromoWidgetDismissed($widgetName);
+ }
+
+ private function isPromoWidgetDismissed(string $widgetName): bool
+ {
+ return Option::get($this->getDismissedWidgetOptionName($widgetName)) > 0;
+ }
+
+ private function getDismissedWidgetOptionName(string $widgetName): string
+ {
+ return sprintf(self::DISMISSED_WIDGET_OPTION_NAME, $widgetName, Piwik::getCurrentUserLogin());
+ }
+}
diff --git a/plugins/ProfessionalServices/Widgets/DismissibleWidget.php b/plugins/ProfessionalServices/Widgets/DismissibleWidget.php
new file mode 100644
index 00000000000..6f8e46b5419
--- /dev/null
+++ b/plugins/ProfessionalServices/Widgets/DismissibleWidget.php
@@ -0,0 +1,24 @@
+setCategoryId('ProfessionalServices_PromoAbTesting');
$config->setSubcategoryId('ProfessionalServices_PromoOverview');
$config->setIsNotWidgetizable();
$promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable');
- $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME);
+ $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName());
$config->setIsEnabled($isEnabled);
}
@@ -37,6 +37,8 @@ public function render()
$view = new View('@ProfessionalServices/pluginAdvertising');
$view->plugin = $pluginInfo;
+ $view->widgetName = self::getDismissibleWidgetName();
+ $view->userCanDismiss = Piwik::isUserIsAnonymous() === false;
$view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', $pluginInfo['displayName']);
$view->listOfFeatures = [
diff --git a/plugins/ProfessionalServices/Widgets/PromoCrashAnalytics.php b/plugins/ProfessionalServices/Widgets/PromoCrashAnalytics.php
index 36395e322dc..9a75e9800c1 100644
--- a/plugins/ProfessionalServices/Widgets/PromoCrashAnalytics.php
+++ b/plugins/ProfessionalServices/Widgets/PromoCrashAnalytics.php
@@ -11,10 +11,9 @@
use Piwik\Container\StaticContainer;
use Piwik\Piwik;
use Piwik\View;
-use Piwik\Widget\Widget;
use Piwik\Widget\WidgetConfig;
-class PromoCrashAnalytics extends Widget
+class PromoCrashAnalytics extends DismissibleWidget
{
private const PROMO_PLUGIN_NAME = 'CrashAnalytics';
@@ -26,7 +25,7 @@ public static function configure(WidgetConfig $config)
$promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable');
- $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME);
+ $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName());
$config->setIsEnabled($isEnabled);
}
@@ -37,6 +36,8 @@ public function render()
$view = new View('@ProfessionalServices/pluginAdvertising');
$view->plugin = $pluginInfo;
+ $view->widgetName = self::getDismissibleWidgetName();
+ $view->userCanDismiss = Piwik::isUserIsAnonymous() === false;
$view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', $pluginInfo['displayName']);
$view->listOfFeatures = [
diff --git a/plugins/ProfessionalServices/Widgets/PromoCustomReports.php b/plugins/ProfessionalServices/Widgets/PromoCustomReports.php
index 6f6e83801ae..93995f6297b 100644
--- a/plugins/ProfessionalServices/Widgets/PromoCustomReports.php
+++ b/plugins/ProfessionalServices/Widgets/PromoCustomReports.php
@@ -11,10 +11,9 @@
use Piwik\Container\StaticContainer;
use Piwik\Piwik;
use Piwik\View;
-use Piwik\Widget\Widget;
use Piwik\Widget\WidgetConfig;
-class PromoCustomReports extends Widget
+class PromoCustomReports extends DismissibleWidget
{
private const PROMO_PLUGIN_NAME = 'CustomReports';
@@ -26,7 +25,7 @@ public static function configure(WidgetConfig $config)
$promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable');
- $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME);
+ $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName());
$config->setIsEnabled($isEnabled);
}
@@ -37,6 +36,8 @@ public function render()
$view = new View('@ProfessionalServices/pluginAdvertising');
$view->plugin = $pluginInfo;
+ $view->widgetName = self::getDismissibleWidgetName();
+ $view->userCanDismiss = Piwik::isUserIsAnonymous() === false;
$view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', $pluginInfo['displayName']);
$view->listOfFeatures = [
diff --git a/plugins/ProfessionalServices/Widgets/PromoFormAnalytics.php b/plugins/ProfessionalServices/Widgets/PromoFormAnalytics.php
index 37ae580ab8e..2deabbb8ac8 100644
--- a/plugins/ProfessionalServices/Widgets/PromoFormAnalytics.php
+++ b/plugins/ProfessionalServices/Widgets/PromoFormAnalytics.php
@@ -11,10 +11,9 @@
use Piwik\Container\StaticContainer;
use Piwik\Piwik;
use Piwik\View;
-use Piwik\Widget\Widget;
use Piwik\Widget\WidgetConfig;
-class PromoFormAnalytics extends Widget
+class PromoFormAnalytics extends DismissibleWidget
{
private const PROMO_PLUGIN_NAME = 'FormAnalytics';
@@ -26,7 +25,7 @@ public static function configure(WidgetConfig $config)
$promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable');
- $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME);
+ $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName());
$config->setIsEnabled($isEnabled);
}
@@ -37,6 +36,8 @@ public function render()
$view = new View('@ProfessionalServices/pluginAdvertising');
$view->plugin = $pluginInfo;
+ $view->widgetName = self::getDismissibleWidgetName();
+ $view->userCanDismiss = Piwik::isUserIsAnonymous() === false;
$view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', $pluginInfo['displayName']);
$view->listOfFeatures = [
diff --git a/plugins/ProfessionalServices/Widgets/PromoFunnels.php b/plugins/ProfessionalServices/Widgets/PromoFunnels.php
index d3c423e56fe..ca3d22e0785 100644
--- a/plugins/ProfessionalServices/Widgets/PromoFunnels.php
+++ b/plugins/ProfessionalServices/Widgets/PromoFunnels.php
@@ -11,10 +11,9 @@
use Piwik\Container\StaticContainer;
use Piwik\Piwik;
use Piwik\View;
-use Piwik\Widget\Widget;
use Piwik\Widget\WidgetConfig;
-class PromoFunnels extends Widget
+class PromoFunnels extends DismissibleWidget
{
private const PROMO_PLUGIN_NAME = 'Funnels';
@@ -26,7 +25,7 @@ public static function configure(WidgetConfig $config)
$promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable');
- $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME);
+ $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName());
$config->setIsEnabled($isEnabled);
}
@@ -37,6 +36,8 @@ public function render()
$view = new View('@ProfessionalServices/pluginAdvertising');
$view->plugin = $pluginInfo;
+ $view->widgetName = self::getDismissibleWidgetName();
+ $view->userCanDismiss = Piwik::isUserIsAnonymous() === false;
$view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', $pluginInfo['displayName']);
$view->listOfFeatures = [
diff --git a/plugins/ProfessionalServices/Widgets/PromoHeatmaps.php b/plugins/ProfessionalServices/Widgets/PromoHeatmaps.php
index 7c68fea2daa..7d634da8d5c 100644
--- a/plugins/ProfessionalServices/Widgets/PromoHeatmaps.php
+++ b/plugins/ProfessionalServices/Widgets/PromoHeatmaps.php
@@ -11,10 +11,9 @@
use Piwik\Container\StaticContainer;
use Piwik\Piwik;
use Piwik\View;
-use Piwik\Widget\Widget;
use Piwik\Widget\WidgetConfig;
-class PromoHeatmaps extends Widget
+class PromoHeatmaps extends DismissibleWidget
{
private const PROMO_PLUGIN_NAME = 'HeatmapSessionRecording';
@@ -26,7 +25,7 @@ public static function configure(WidgetConfig $config)
$promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable');
- $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME);
+ $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName());
$config->setIsEnabled($isEnabled);
}
@@ -37,6 +36,8 @@ public function render()
$view = new View('@ProfessionalServices/pluginAdvertising');
$view->plugin = $pluginInfo;
+ $view->widgetName = self::getDismissibleWidgetName();
+ $view->userCanDismiss = Piwik::isUserIsAnonymous() === false;
$view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', 'Heatmaps');
$view->imageName = 'ad-heatmaps.png';
diff --git a/plugins/ProfessionalServices/Widgets/PromoMediaAnalytics.php b/plugins/ProfessionalServices/Widgets/PromoMediaAnalytics.php
index 0081aa29d7d..4acb72bd65a 100644
--- a/plugins/ProfessionalServices/Widgets/PromoMediaAnalytics.php
+++ b/plugins/ProfessionalServices/Widgets/PromoMediaAnalytics.php
@@ -11,10 +11,9 @@
use Piwik\Container\StaticContainer;
use Piwik\Piwik;
use Piwik\View;
-use Piwik\Widget\Widget;
use Piwik\Widget\WidgetConfig;
-class PromoMediaAnalytics extends Widget
+class PromoMediaAnalytics extends DismissibleWidget
{
private const PROMO_PLUGIN_NAME = 'MediaAnalytics';
@@ -26,7 +25,7 @@ public static function configure(WidgetConfig $config)
$promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable');
- $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME);
+ $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName());
$config->setIsEnabled($isEnabled);
}
@@ -37,6 +36,8 @@ public function render()
$view = new View('@ProfessionalServices/pluginAdvertising');
$view->plugin = $pluginInfo;
+ $view->widgetName = self::getDismissibleWidgetName();
+ $view->userCanDismiss = Piwik::isUserIsAnonymous() === false;
$view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', $pluginInfo['displayName']);
$view->listOfFeatures = [
diff --git a/plugins/ProfessionalServices/Widgets/PromoSessionRecordings.php b/plugins/ProfessionalServices/Widgets/PromoSessionRecordings.php
index 585411d629a..b76244f0499 100644
--- a/plugins/ProfessionalServices/Widgets/PromoSessionRecordings.php
+++ b/plugins/ProfessionalServices/Widgets/PromoSessionRecordings.php
@@ -11,10 +11,9 @@
use Piwik\Container\StaticContainer;
use Piwik\Piwik;
use Piwik\View;
-use Piwik\Widget\Widget;
use Piwik\Widget\WidgetConfig;
-class PromoSessionRecordings extends Widget
+class PromoSessionRecordings extends DismissibleWidget
{
private const PROMO_PLUGIN_NAME = 'HeatmapSessionRecording';
@@ -26,7 +25,7 @@ public static function configure(WidgetConfig $config)
$promoWidgetApplicable = StaticContainer::get('Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable');
- $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME);
+ $isEnabled = $promoWidgetApplicable->check(self::PROMO_PLUGIN_NAME, self::getDismissibleWidgetName());
$config->setIsEnabled($isEnabled);
}
@@ -37,6 +36,8 @@ public function render()
$view = new View('@ProfessionalServices/pluginAdvertising');
$view->plugin = $pluginInfo;
+ $view->widgetName = self::getDismissibleWidgetName();
+ $view->userCanDismiss = Piwik::isUserIsAnonymous() === false;
$view->title = Piwik::translate('ProfessionalServices_PromoUnlockPowerOf', 'Session Recordings'); // custom title
$view->imageName = 'ad-sessionrecordings.png';
diff --git a/plugins/ProfessionalServices/lang/en.json b/plugins/ProfessionalServices/lang/en.json
index 15e97218a67..b41af2077b9 100644
--- a/plugins/ProfessionalServices/lang/en.json
+++ b/plugins/ProfessionalServices/lang/en.json
@@ -12,6 +12,8 @@
"PromoCustomReports": "Custom Reports",
"PromoCrashAnalytics": "Crashes",
"PromoUnlockPowerOf" : "Unlock the Power of %1$s",
+ "DismissPromoWidget": "If this feature is not relevant to your goals, you can %1$shide this section%2$s.",
+ "DismissedNotification": "The %1$s menu will no longer be shown, unless the plugin is installed on your Matomo instance.",
"AltTextPreviewImageFor": "Preview image for %1$s",
"CTAStartFreeTrial": "Start free trial",
"CTALearnMore": "Learn more about %1$s",
diff --git a/plugins/ProfessionalServices/stylesheets/promos.less b/plugins/ProfessionalServices/stylesheets/promos.less
index 5de43862b11..a32d8812c01 100644
--- a/plugins/ProfessionalServices/stylesheets/promos.less
+++ b/plugins/ProfessionalServices/stylesheets/promos.less
@@ -67,4 +67,10 @@
text-decoration: none;
}
}
+
+ .promo-dismiss {
+ padding-top: 16px;
+ .font-default(12px; 21px);
+ color: @color-silver-l40;
+ }
}
diff --git a/plugins/ProfessionalServices/templates/pluginAdvertising.twig b/plugins/ProfessionalServices/templates/pluginAdvertising.twig
index 5f4d678081c..c736c8d242d 100644
--- a/plugins/ProfessionalServices/templates/pluginAdvertising.twig
+++ b/plugins/ProfessionalServices/templates/pluginAdvertising.twig
@@ -24,4 +24,9 @@
{{ 'CoreAdminHome_LearnMore'|translate }}
+ {% if userCanDismiss %}
+
+ {% endif %}
diff --git a/plugins/ProfessionalServices/tests/UI/ProfessionalServices_PluginPromo_spec.js b/plugins/ProfessionalServices/tests/UI/ProfessionalServices_PluginPromo_spec.js
index 443df27b276..913ac55eb52 100644
--- a/plugins/ProfessionalServices/tests/UI/ProfessionalServices_PluginPromo_spec.js
+++ b/plugins/ProfessionalServices/tests/UI/ProfessionalServices_PluginPromo_spec.js
@@ -96,4 +96,20 @@ describe("ProfessionalServices_PluginPromo", function () {
expect(await page.screenshotSelector('.pluginPromo')).to.matchImage('promo_sessionrecordings');
});
+ it('can dismiss a promo and no longer see it in menu', async function() {
+ const category = 'ProfessionalServices_PromoFormAnalytics';
+ const subcategory = 'ProfessionalServices_PromoOverview';
+
+ await page.goto(urlBase + 'category=' + category + '&subcategory=' + subcategory);
+ await page.waitForNetworkIdle();
+
+ let promoSessionRecordingMenuItemCount = await page.evaluate(() => $('li.menuTab[data-category-id=ProfessionalServices_PromoFormAnalytics]').length);
+ expect(promoSessionRecordingMenuItemCount).to.equal(1);
+
+ await page.click('.promo-dismiss a');
+ await page.waitForNetworkIdle();
+
+ promoSessionRecordingMenuItemCount = await page.evaluate(() => $('li.menuTab[data-category-id=ProfessionalServices_PromoFormAnalytics]').length);
+ expect(promoSessionRecordingMenuItemCount).to.equal(0);
+ });
});
diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_abtesting.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_abtesting.png
index b35d4eafe47..28b1d2bd4b2 100644
--- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_abtesting.png
+++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_abtesting.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:13d58566743a89e7f492bbde6001739b9cb6a844a4bf31bce6c40aadfd646b48
-size 83141
+oid sha256:84016295b2dc749cedc975aad29a732e32aa89ab40d4098632b69d72072caf26
+size 87834
diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_crashanalytics.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_crashanalytics.png
index b1ac1a9578d..81eb54f62ac 100644
--- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_crashanalytics.png
+++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_crashanalytics.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:1eac263f94472b943f4fd2fd1c5759afd2558bc8ea2ca2ea413c12e0bee296dc
-size 98768
+oid sha256:c8523ca18b34a5032fc622d61439079d046b0137e9de77f75c4c165790b0cd05
+size 103329
diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_customreports.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_customreports.png
index e74417a269f..907966cc941 100644
--- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_customreports.png
+++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_customreports.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:e8e22f3b36a40ffe9eec7d4d27a711795464cc9ee3eeca080d475248e67c8935
-size 81156
+oid sha256:f473c52eab9d28e23da84d1154345d5669610a30b111423340694e24f16a13a4
+size 85649
diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_formanalytics.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_formanalytics.png
index fce63fc2b1c..b32a303aa62 100644
--- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_formanalytics.png
+++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_formanalytics.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:8e28a3ceefb283488f5d011e446128053b47fd544c5da80efecc1adcda1c59a6
-size 71249
+oid sha256:60ee01f775e940a562d5127c6a837840c49d1f4c5932f0fa30e65a79b853f958
+size 75802
diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_funnels.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_funnels.png
index 269e3db698e..f0e08f283f8 100644
--- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_funnels.png
+++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_funnels.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5c5359a52f6b85f8d7988bad68b6c652242bcd2af53ec72289c85ceb8c9507f0
-size 72716
+oid sha256:0e2277d0be470ed28ef2d74046f4eaedc2fa72535f84d5e0c9ee74fb3351f8b3
+size 77299
diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_heatmaps.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_heatmaps.png
index 4716143f237..2f05092edff 100644
--- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_heatmaps.png
+++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_heatmaps.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:de621f7992c3cf2451d7595c8ec394cc96e16b00d346b2d38897aec2f53ddce8
-size 189642
+oid sha256:9ac6c8166abc6947289f8ca0b7ac0fc253d28032c9555c387ea9489accdd27b4
+size 194243
diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_mediaanalytics.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_mediaanalytics.png
index 25aa4da060e..c54497d1360 100644
--- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_mediaanalytics.png
+++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_mediaanalytics.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9dc20f0478bafa7b1eba2fc2920fcf6ba09fb014c839a8db39ff131e6be2ffe4
-size 99660
+oid sha256:1a7bf23650e7283c689d6e586c05a5dbf2c41a93539488912424a2bd3371dcd5
+size 104246
diff --git a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_sessionrecordings.png b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_sessionrecordings.png
index fd137332373..593b4728e0e 100644
--- a/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_sessionrecordings.png
+++ b/plugins/ProfessionalServices/tests/UI/expected-screenshots/ProfessionalServices_PluginPromo_promo_sessionrecordings.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:90105ff967da90c4e73f064ed24c65d997b4b57e3f05c189c94a1b056c92282a
-size 143124
+oid sha256:bd751f53798b043f9848ad2a2b609df9e05ef875fbd87dc4886d002963fbffde
+size 147677
diff --git a/plugins/ProfessionalServices/tests/Unit/PromoWidgetApplicableTest.php b/plugins/ProfessionalServices/tests/Unit/PromoWidgetApplicableTest.php
index 575822e3a57..1626238dfcf 100644
--- a/plugins/ProfessionalServices/tests/Unit/PromoWidgetApplicableTest.php
+++ b/plugins/ProfessionalServices/tests/Unit/PromoWidgetApplicableTest.php
@@ -12,13 +12,14 @@
use Piwik\Config;
use Piwik\Plugin\Manager;
use Piwik\Plugins\ProfessionalServices\PromoWidgetApplicable;
+use Piwik\Plugins\ProfessionalServices\PromoWidgetDismissal;
class PromoWidgetApplicableTest extends TestCase
{
/**
* @dataProvider checkDataProvider
*/
- public function test_check_shouldOnlyReturnTrue_IfAdShouldBeShown(bool $adsForProfessionalServicesEnabled, bool $marketplaceEnabled, bool $internetAccessEnabled, bool $pluginActivated, bool $expected): void
+ public function test_check_shouldOnlyReturnTrue_IfAdShouldBeShown(bool $adsForProfessionalServicesEnabled, bool $marketplaceEnabled, bool $internetAccessEnabled, bool $pluginActivated, bool $isDismissed, bool $expected): void
{
$manager = $this->createMock(Manager::class);
$manager->method('isPluginActivated')->willReturnMap(
@@ -35,27 +36,33 @@ public function test_check_shouldOnlyReturnTrue_IfAdShouldBeShown(bool $adsForPr
'piwik_professional_support_ads_enabled' => $adsForProfessionalServicesEnabled,
]);
- $sut = new PromoWidgetApplicable($manager, $config);
- $this->assertEquals($expected, $sut->check('MyPlugin'));
+ $promoWidgetDismissal = $this->createMock(PromoWidgetDismissal::class);
+ $promoWidgetDismissal->method('isPromoWidgetDismissedForCurrentUser')
+ ->with('Any')
+ ->willReturn($isDismissed);
+
+ $sut = new PromoWidgetApplicable($manager, $config, $promoWidgetDismissal);
+ $this->assertEquals($expected, $sut->check('MyPlugin', 'Any'));
}
protected function checkDataProvider(): \Generator
{
- yield [true, true, true, true, false];
- yield [true, true, true, false, true];
- yield [true, true, false, true, false];
- yield [true, true, false, false, false];
- yield [true, false, true, true, false];
- yield [true, false, true, false, false];
- yield [true, false, false, true, false];
- yield [true, false, false, false, false];
- yield [false, true, true, true, false];
- yield [false, true, true, false, false];
- yield [false, true, false, true, false];
- yield [false, true, false, false, false];
- yield [false, false, true, true, false];
- yield [false, false, true, false, false];
- yield [false, false, false, true, false];
- yield [false, false, false, false, false];
+ yield [true, true, true, true, false, false];
+ yield [true, true, true, false, false, true];
+ yield [true, true, false, true, false, false];
+ yield [true, true, false, false, false, false];
+ yield [true, false, true, true, false, false];
+ yield [true, false, true, false, false, false];
+ yield [true, false, false, true, false, false];
+ yield [true, false, false, false, false, false];
+ yield [false, true, true, true, false, false];
+ yield [false, true, true, false, false, false];
+ yield [false, true, false, true, false, false];
+ yield [false, true, false, false, false, false];
+ yield [false, false, true, true, false, false];
+ yield [false, false, true, false, false, false];
+ yield [false, false, false, true, false, false];
+ yield [false, false, false, false, false, false];
+ yield [true, true, true, false, true, false];
}
}
diff --git a/plugins/ProfessionalServices/vue/dist/ProfessionalServices.umd.js b/plugins/ProfessionalServices/vue/dist/ProfessionalServices.umd.js
new file mode 100644
index 00000000000..a783ee6e796
--- /dev/null
+++ b/plugins/ProfessionalServices/vue/dist/ProfessionalServices.umd.js
@@ -0,0 +1,204 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+ if(typeof exports === 'object' && typeof module === 'object')
+ module.exports = factory(require("CoreHome"));
+ else if(typeof define === 'function' && define.amd)
+ define(["CoreHome"], factory);
+ else if(typeof exports === 'object')
+ exports["ProfessionalServices"] = factory(require("CoreHome"));
+ else
+ root["ProfessionalServices"] = factory(root["CoreHome"]);
+})((typeof self !== 'undefined' ? self : this), function(__WEBPACK_EXTERNAL_MODULE__19dc__) {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId]) {
+/******/ return installedModules[moduleId].exports;
+/******/ }
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ i: moduleId,
+/******/ l: false,
+/******/ exports: {}
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.l = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // define getter function for harmony exports
+/******/ __webpack_require__.d = function(exports, name, getter) {
+/******/ if(!__webpack_require__.o(exports, name)) {
+/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
+/******/ }
+/******/ };
+/******/
+/******/ // define __esModule on exports
+/******/ __webpack_require__.r = function(exports) {
+/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ }
+/******/ Object.defineProperty(exports, '__esModule', { value: true });
+/******/ };
+/******/
+/******/ // create a fake namespace object
+/******/ // mode & 1: value is a module id, require it
+/******/ // mode & 2: merge all properties of value into the ns
+/******/ // mode & 4: return value when already ns object
+/******/ // mode & 8|1: behave like require
+/******/ __webpack_require__.t = function(value, mode) {
+/******/ if(mode & 1) value = __webpack_require__(value);
+/******/ if(mode & 8) return value;
+/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
+/******/ var ns = Object.create(null);
+/******/ __webpack_require__.r(ns);
+/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
+/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
+/******/ return ns;
+/******/ };
+/******/
+/******/ // getDefaultExport function for compatibility with non-harmony modules
+/******/ __webpack_require__.n = function(module) {
+/******/ var getter = module && module.__esModule ?
+/******/ function getDefault() { return module['default']; } :
+/******/ function getModuleExports() { return module; };
+/******/ __webpack_require__.d(getter, 'a', getter);
+/******/ return getter;
+/******/ };
+/******/
+/******/ // Object.prototype.hasOwnProperty.call
+/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "plugins/ProfessionalServices/vue/dist/";
+/******/
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(__webpack_require__.s = "fae3");
+/******/ })
+/************************************************************************/
+/******/ ({
+
+/***/ "19dc":
+/***/ (function(module, exports) {
+
+module.exports = __WEBPACK_EXTERNAL_MODULE__19dc__;
+
+/***/ }),
+
+/***/ "fae3":
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+// ESM COMPAT FLAG
+__webpack_require__.r(__webpack_exports__);
+
+// EXPORTS
+__webpack_require__.d(__webpack_exports__, "DismissPromoWidget", function() { return /* reexport */ DismissPromoWidget; });
+
+// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/setPublicPath.js
+// This file is imported into lib/wc client bundles.
+
+if (typeof window !== 'undefined') {
+ var currentScript = window.document.currentScript
+ if (false) { var getCurrentScript; }
+
+ var src = currentScript && currentScript.src.match(/(.+\/)[^/]+\.js(\?.*)?$/)
+ if (src) {
+ __webpack_require__.p = src[1] // eslint-disable-line
+ }
+}
+
+// Indicate to webpack that this file can be concatenated
+/* harmony default export */ var setPublicPath = (null);
+
+// EXTERNAL MODULE: external "CoreHome"
+var external_CoreHome_ = __webpack_require__("19dc");
+
+// CONCATENATED MODULE: ./plugins/ProfessionalServices/vue/src/DismissPromoWidget/DismissPromoWidget.ts
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+
+function onClickDismissPromoWidgetLink(binding, event) {
+ var widgetName = binding.value.widgetName;
+ var currentCategory = external_CoreHome_["ReportingMenuStore"].activeCategory.value;
+ event.preventDefault();
+ external_CoreHome_["Matomo"].helper.showAjaxLoading();
+ return external_CoreHome_["AjaxHelper"].post({
+ method: 'ProfessionalServices.dismissWidget'
+ }, {
+ widgetName: widgetName
+ }).catch(function (e) {
+ external_CoreHome_["Matomo"].helper.hideAjaxLoading();
+ throw e;
+ }).then(function () {
+ external_CoreHome_["ReportingMenuStore"].reloadMenuItems().then(function () {
+ external_CoreHome_["Matomo"].helper.hideAjaxLoading();
+ external_CoreHome_["MatomoUrl"].updateHash('category=Dashboard_Dashboard&subcategory=1');
+ external_CoreHome_["NotificationsStore"].show({
+ id: 'ProfessionalServices_PromoWidgetDismissed',
+ animate: false,
+ context: 'info',
+ noclear: true,
+ message: Object(external_CoreHome_["translate"])('ProfessionalServices_DismissedNotification', Object(external_CoreHome_["translate"])(currentCategory)),
+ type: 'toast'
+ });
+ });
+ });
+}
+
+/* harmony default export */ var DismissPromoWidget = ({
+ mounted: function mounted(element, binding) {
+ var widgetName = binding.value.widgetName;
+
+ if (!widgetName) {
+ return;
+ }
+
+ binding.value.onClickHandler = onClickDismissPromoWidgetLink.bind(null, binding);
+ element.addEventListener('click', binding.value.onClickHandler);
+ },
+ unmounted: function unmounted(element, binding) {
+ element.removeEventListener('click', binding.value.onClickHandler);
+ }
+});
+// CONCATENATED MODULE: ./plugins/ProfessionalServices/vue/src/index.ts
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/entry-lib-no-default.js
+
+
+
+
+/***/ })
+
+/******/ });
+});
+//# sourceMappingURL=ProfessionalServices.umd.js.map
\ No newline at end of file
diff --git a/plugins/ProfessionalServices/vue/dist/ProfessionalServices.umd.min.js b/plugins/ProfessionalServices/vue/dist/ProfessionalServices.umd.min.js
new file mode 100644
index 00000000000..f55d9b78066
--- /dev/null
+++ b/plugins/ProfessionalServices/vue/dist/ProfessionalServices.umd.min.js
@@ -0,0 +1,15 @@
+(function(e,t){"object"===typeof exports&&"object"===typeof module?module.exports=t(require("CoreHome")):"function"===typeof define&&define.amd?define(["CoreHome"],t):"object"===typeof exports?exports["ProfessionalServices"]=t(require("CoreHome")):e["ProfessionalServices"]=t(e["CoreHome"])})("undefined"!==typeof self?self:this,(function(e){return function(e){var t={};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}return o.m=e,o.c=t,o.d=function(e,t,n){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},o.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,t){if(1&t&&(e=o(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)o.d(n,r,function(t){return e[t]}.bind(null,r));return n},o.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="plugins/ProfessionalServices/vue/dist/",o(o.s="fae3")}({"19dc":function(t,o){t.exports=e},fae3:function(e,t,o){"use strict";if(o.r(t),o.d(t,"DismissPromoWidget",(function(){return u})),"undefined"!==typeof window){var n=window.document.currentScript,r=n&&n.src.match(/(.+\/)[^/]+\.js(\?.*)?$/);r&&(o.p=r[1])}var i=o("19dc");
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+function a(e,t){var o=e.value.widgetName,n=i["ReportingMenuStore"].activeCategory.value;return t.preventDefault(),i["Matomo"].helper.showAjaxLoading(),i["AjaxHelper"].post({method:"ProfessionalServices.dismissWidget"},{widgetName:o}).catch((function(e){throw i["Matomo"].helper.hideAjaxLoading(),e})).then((function(){i["ReportingMenuStore"].reloadMenuItems().then((function(){i["Matomo"].helper.hideAjaxLoading(),i["MatomoUrl"].updateHash("category=Dashboard_Dashboard&subcategory=1"),i["NotificationsStore"].show({id:"ProfessionalServices_PromoWidgetDismissed",animate:!1,context:"info",noclear:!0,message:Object(i["translate"])("ProfessionalServices_DismissedNotification",Object(i["translate"])(n)),type:"toast"})}))}))}var u={mounted:function(e,t){var o=t.value.widgetName;o&&(t.value.onClickHandler=a.bind(null,t),e.addEventListener("click",t.value.onClickHandler))},unmounted:function(e,t){e.removeEventListener("click",t.value.onClickHandler)}};
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */}})}));
+//# sourceMappingURL=ProfessionalServices.umd.min.js.map
\ No newline at end of file
diff --git a/plugins/ProfessionalServices/vue/dist/umd.metadata.json b/plugins/ProfessionalServices/vue/dist/umd.metadata.json
new file mode 100644
index 00000000000..9ecfcc04562
--- /dev/null
+++ b/plugins/ProfessionalServices/vue/dist/umd.metadata.json
@@ -0,0 +1,5 @@
+{
+ "dependsOn": [
+ "CoreHome"
+ ]
+}
\ No newline at end of file
diff --git a/plugins/ProfessionalServices/vue/src/DismissPromoWidget/DismissPromoWidget.ts b/plugins/ProfessionalServices/vue/src/DismissPromoWidget/DismissPromoWidget.ts
new file mode 100644
index 00000000000..581f276c14d
--- /dev/null
+++ b/plugins/ProfessionalServices/vue/src/DismissPromoWidget/DismissPromoWidget.ts
@@ -0,0 +1,73 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+import { DirectiveBinding } from 'vue';
+import {
+ AjaxHelper,
+ Matomo,
+ MatomoUrl,
+ NotificationsStore,
+ ReportingMenuStore,
+ translate,
+} from 'CoreHome';
+
+interface DismissPromoWidgetDirectiveValue {
+ widgetName: string;
+ onClickHandler?: (event: Event) => void;
+}
+
+function onClickDismissPromoWidgetLink(
+ binding: DirectiveBinding,
+ event: Event,
+) {
+ const { widgetName } = binding.value;
+ const currentCategory = ReportingMenuStore.activeCategory.value as string;
+
+ event.preventDefault();
+
+ Matomo.helper.showAjaxLoading();
+
+ return AjaxHelper.post({
+ method: 'ProfessionalServices.dismissWidget',
+ }, {
+ widgetName,
+ }).catch((e) => {
+ Matomo.helper.hideAjaxLoading();
+ throw e;
+ }).then(() => {
+ ReportingMenuStore.reloadMenuItems().then(() => {
+ Matomo.helper.hideAjaxLoading();
+ MatomoUrl.updateHash('category=Dashboard_Dashboard&subcategory=1');
+ NotificationsStore.show({
+ id: 'ProfessionalServices_PromoWidgetDismissed',
+ animate: false,
+ context: 'info',
+ noclear: true,
+ message: translate('ProfessionalServices_DismissedNotification', translate(currentCategory)),
+ type: 'toast',
+ });
+ });
+ });
+}
+
+export default {
+ mounted(element: HTMLElement, binding: DirectiveBinding): void {
+ const { widgetName } = binding.value;
+ if (!widgetName) {
+ return;
+ }
+
+ binding.value.onClickHandler = onClickDismissPromoWidgetLink.bind(null, binding);
+ element.addEventListener('click', binding.value.onClickHandler!);
+ },
+ unmounted(
+ element: HTMLElement,
+ binding: DirectiveBinding,
+ ): void {
+ element.removeEventListener('click', binding.value.onClickHandler!);
+ },
+};
diff --git a/plugins/ProfessionalServices/vue/src/index.ts b/plugins/ProfessionalServices/vue/src/index.ts
new file mode 100644
index 00000000000..f65ba894314
--- /dev/null
+++ b/plugins/ProfessionalServices/vue/src/index.ts
@@ -0,0 +1,8 @@
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+export { default as DismissPromoWidget } from './DismissPromoWidget/DismissPromoWidget';