From 7578b50b4bd21c664c9dc8bffc618591b667f949 Mon Sep 17 00:00:00 2001
From: Alexis Guyomar
Date: Tue, 3 Dec 2024 10:41:32 +0100
Subject: [PATCH 01/18] feat: add url utils
---
_dev/src/ts/utils/urlUtils.ts | 8 ++++
_dev/tests/utils/urlUtils.test.ts | 70 +++++++++++++++++++++++++++++++
2 files changed, 78 insertions(+)
create mode 100644 _dev/src/ts/utils/urlUtils.ts
create mode 100644 _dev/tests/utils/urlUtils.test.ts
diff --git a/_dev/src/ts/utils/urlUtils.ts b/_dev/src/ts/utils/urlUtils.ts
new file mode 100644
index 000000000..85a436fc4
--- /dev/null
+++ b/_dev/src/ts/utils/urlUtils.ts
@@ -0,0 +1,8 @@
+export function maskSensitiveInfoInUrl(url: string, adminFolder: string): string {
+ const placeHolder = '********';
+ const adminFolderRegex = new RegExp(adminFolder, 'g');
+ const maskedUrl = url.replace(adminFolderRegex, placeHolder);
+
+ const tokenRegex = new RegExp('&token=[^&]*', 'gi');
+ return maskedUrl.replace(tokenRegex, `&token=${placeHolder}`);
+}
diff --git a/_dev/tests/utils/urlUtils.test.ts b/_dev/tests/utils/urlUtils.test.ts
new file mode 100644
index 000000000..4e13059fe
--- /dev/null
+++ b/_dev/tests/utils/urlUtils.test.ts
@@ -0,0 +1,70 @@
+import { maskSensitiveInfoInUrl } from '../../src/ts/utils/urlUtils';
+
+describe('urlUtils', () => {
+ describe('maskSensitiveInfoInUrl', () => {
+ const adminFolder = 'admin-dev';
+
+ test('URL with admin folder and token', () => {
+ const url =
+ 'http://myshop.com/admin-dev/index.php?controller=AdminSelfUpgrade&token=831ecc0c2e1c41af40cee361afec03f3&route=home-page';
+ expect(maskSensitiveInfoInUrl(url, adminFolder)).toBe(
+ 'http://myshop.com/********/index.php?controller=AdminSelfUpgrade&token=********&route=home-page'
+ );
+ });
+
+ test('URL without token', () => {
+ const url =
+ 'http://myshop.com/admin-dev/index.php?controller=AdminSelfUpgrade&route=home-page';
+ expect(maskSensitiveInfoInUrl(url, adminFolder)).toBe(
+ 'http://myshop.com/********/index.php?controller=AdminSelfUpgrade&route=home-page'
+ );
+ });
+
+ test('URL with token but without admin folder', () => {
+ const url =
+ 'http://myshop.com/index.php?controller=AdminSelfUpgrade&token=831ecc0c2e1c41af40cee361afec03f3&route=home-page';
+ expect(maskSensitiveInfoInUrl(url, adminFolder)).toBe(
+ 'http://myshop.com/index.php?controller=AdminSelfUpgrade&token=********&route=home-page'
+ );
+ });
+
+ test('URL without admin folder & token', () => {
+ const url = 'http://myshop.com/index.php?controller=AdminSelfUpgrade&route=home-page';
+ expect(maskSensitiveInfoInUrl(url, adminFolder)).toBe(
+ 'http://myshop.com/index.php?controller=AdminSelfUpgrade&route=home-page'
+ );
+ });
+
+ test('URL with multiple admin folder occurrence', () => {
+ const url =
+ 'http://myshop.com/admin-dev/admin-dev/index.php?controller=AdminSelfUpgrade&token=831ecc0c2e1c41af40cee361afec03f3&route=home-page';
+ expect(maskSensitiveInfoInUrl(url, adminFolder)).toBe(
+ 'http://myshop.com/********/********/index.php?controller=AdminSelfUpgrade&token=********&route=home-page'
+ );
+ });
+
+ test('URL with empty token', () => {
+ const url =
+ 'http://myshop.com/admin-dev/index.php?controller=AdminSelfUpgrade&token=&route=home-page';
+ expect(maskSensitiveInfoInUrl(url, adminFolder)).toBe(
+ 'http://myshop.com/********/index.php?controller=AdminSelfUpgrade&token=********&route=home-page'
+ );
+ });
+
+ test('URL with similar token parameter', () => {
+ const url =
+ 'http://myshop.com/admin-dev/index.php?controller=AdminSelfUpgrade&mytoken=12345&route=home-page';
+ expect(maskSensitiveInfoInUrl(url, adminFolder)).toBe(
+ 'http://myshop.com/********/index.php?controller=AdminSelfUpgrade&mytoken=12345&route=home-page'
+ );
+ });
+
+ test('Case sensitivity for token parameter', () => {
+ const url =
+ 'http://myshop.com/admin-dev/index.php?controller=AdminSelfUpgrade&TOKEN=831ecc0c2e1c41af40cee361afec03f3&route=home-page';
+ expect(maskSensitiveInfoInUrl(url, adminFolder)).toBe(
+ 'http://myshop.com/********/index.php?controller=AdminSelfUpgrade&token=********&route=home-page'
+ );
+ });
+ });
+});
From 3b044fb48a93dc0781f4a739a2aa8df61221d419 Mon Sep 17 00:00:00 2001
From: Alexis Guyomar
Date: Tue, 3 Dec 2024 10:46:11 +0100
Subject: [PATCH 02/18] feat: add module version to js variables
---
_dev/global.d.ts | 1 +
_dev/jest.setup.ts | 3 ++-
controllers/admin/AdminSelfUpgradeController.php | 1 +
3 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/_dev/global.d.ts b/_dev/global.d.ts
index 668837dc1..5f93df76e 100644
--- a/_dev/global.d.ts
+++ b/_dev/global.d.ts
@@ -5,6 +5,7 @@ interface AutoUpgradeVariables {
admin_url: string;
admin_dir: string;
stepper_parent_id: string;
+ module_version: string;
}
declare global {
diff --git a/_dev/jest.setup.ts b/_dev/jest.setup.ts
index 57a0f94d7..c65b6d285 100644
--- a/_dev/jest.setup.ts
+++ b/_dev/jest.setup.ts
@@ -3,7 +3,8 @@ window.AutoUpgradeVariables = {
token: 'test-token',
admin_url: 'http://localhost',
admin_dir: '/admin_directory',
- stepper_parent_id: 'stepper_content'
+ stepper_parent_id: 'stepper_content',
+ module_version: '7.1.0'
};
beforeAll(() => {});
diff --git a/controllers/admin/AdminSelfUpgradeController.php b/controllers/admin/AdminSelfUpgradeController.php
index d683fa994..e4e5d75f8 100644
--- a/controllers/admin/AdminSelfUpgradeController.php
+++ b/controllers/admin/AdminSelfUpgradeController.php
@@ -539,6 +539,7 @@ private function getScriptsVariables()
'admin_url' => __PS_BASE_URI__ . $adminDir,
'admin_dir' => $adminDir,
'stepper_parent_id' => \PrestaShop\Module\AutoUpgrade\Twig\PageSelectors::STEPPER_PARENT_ID,
+ 'module_version' => $this->module->version,
];
}
From 7dca01fcdcb5a71e1e54678ba20c3076da7c4d93 Mon Sep 17 00:00:00 2001
From: Alexis Guyomar
Date: Tue, 3 Dec 2024 10:47:28 +0100
Subject: [PATCH 03/18] feat: add sentry api
---
_dev/package-lock.json | 86 +++++++++++++++++++++++++++++
_dev/package.json | 1 +
_dev/src/ts/api/sentryApi.ts | 102 +++++++++++++++++++++++++++++++++++
3 files changed, 189 insertions(+)
create mode 100644 _dev/src/ts/api/sentryApi.ts
diff --git a/_dev/package-lock.json b/_dev/package-lock.json
index 6182f541f..4a741ed33 100644
--- a/_dev/package-lock.json
+++ b/_dev/package-lock.json
@@ -7,6 +7,7 @@
"name": "autoupgrade",
"license": "AFL",
"dependencies": {
+ "@sentry/browser": "^8.41.0",
"axios": "^1.7.7"
},
"devDependencies": {
@@ -2244,6 +2245,91 @@
"win32"
]
},
+ "node_modules/@sentry-internal/browser-utils": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.41.0.tgz",
+ "integrity": "sha512-nU7Bn3jEUmf1QXRUT3j2ewUBlFJpe9vnAnjqpeVPDWTsVI52BwVNcJHuE37PrGs66OZ1ZkGMfKnQk43oCAa+oQ==",
+ "dependencies": {
+ "@sentry/core": "8.41.0",
+ "@sentry/types": "8.41.0"
+ },
+ "engines": {
+ "node": ">=14.18"
+ }
+ },
+ "node_modules/@sentry-internal/feedback": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.41.0.tgz",
+ "integrity": "sha512-bw+BrSNw8abOnu/IpD8YSbYubXkkT8jyNS7TM4e4UPZMuXcbtia7/r5d7kAiUfKv/sV5PNMlZLOk+EYJeLTANg==",
+ "dependencies": {
+ "@sentry/core": "8.41.0",
+ "@sentry/types": "8.41.0"
+ },
+ "engines": {
+ "node": ">=14.18"
+ }
+ },
+ "node_modules/@sentry-internal/replay": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.41.0.tgz",
+ "integrity": "sha512-ByXEY7JI95y4Qr9fS3d28l9uuVU5Qa0HgL+xDmYElNx7CXz3Q9hFN6ibgUeC3h8BO5pDULxWNgAppl7FRY8N5w==",
+ "dependencies": {
+ "@sentry-internal/browser-utils": "8.41.0",
+ "@sentry/core": "8.41.0",
+ "@sentry/types": "8.41.0"
+ },
+ "engines": {
+ "node": ">=14.18"
+ }
+ },
+ "node_modules/@sentry-internal/replay-canvas": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.41.0.tgz",
+ "integrity": "sha512-lpgOBHWr1ZNxidD72A2pfoUMjIpwonOPYoQZWAHr86Oa3eIVQOyfklZlHW+gKPFl2/IEl9Lbtcke0JiDp3dkIQ==",
+ "dependencies": {
+ "@sentry-internal/replay": "8.41.0",
+ "@sentry/core": "8.41.0",
+ "@sentry/types": "8.41.0"
+ },
+ "engines": {
+ "node": ">=14.18"
+ }
+ },
+ "node_modules/@sentry/browser": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.41.0.tgz",
+ "integrity": "sha512-FfAU55eYwW2lG4M3dEw2472RvHrD5YWSfHCZvuRf/4skX38kFvKghZQ+epL+CVHTzvIRHOrbj8qQK6YLTGl9ew==",
+ "dependencies": {
+ "@sentry-internal/browser-utils": "8.41.0",
+ "@sentry-internal/feedback": "8.41.0",
+ "@sentry-internal/replay": "8.41.0",
+ "@sentry-internal/replay-canvas": "8.41.0",
+ "@sentry/core": "8.41.0",
+ "@sentry/types": "8.41.0"
+ },
+ "engines": {
+ "node": ">=14.18"
+ }
+ },
+ "node_modules/@sentry/core": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.41.0.tgz",
+ "integrity": "sha512-3v7u3t4LozCA5SpZY4yqUN2U3jSrkXNoLgz6L2SUUiydyCuSwXZIFEwpLJfgQyidpNDifeQbBI5E1O910XkPsA==",
+ "dependencies": {
+ "@sentry/types": "8.41.0"
+ },
+ "engines": {
+ "node": ">=14.18"
+ }
+ },
+ "node_modules/@sentry/types": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.41.0.tgz",
+ "integrity": "sha512-eqdnGr9k9H++b9CjVUoTNUVahPVWeNnMy0YGkqS5+cjWWC+x43p56202oidGFmWo6702ub/xwUNH6M5PC4kq6A==",
+ "engines": {
+ "node": ">=14.18"
+ }
+ },
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
diff --git a/_dev/package.json b/_dev/package.json
index 5c36df58e..e33f99b83 100644
--- a/_dev/package.json
+++ b/_dev/package.json
@@ -48,6 +48,7 @@
"vite-plugin-static-copy": "^1.0.6"
},
"dependencies": {
+ "@sentry/browser": "^8.41.0",
"axios": "^1.7.7"
}
}
diff --git a/_dev/src/ts/api/sentryApi.ts b/_dev/src/ts/api/sentryApi.ts
new file mode 100644
index 000000000..d59720ec4
--- /dev/null
+++ b/_dev/src/ts/api/sentryApi.ts
@@ -0,0 +1,102 @@
+import * as Sentry from '@sentry/browser';
+import { SeverityLevel } from '@sentry/browser';
+import { maskSensitiveInfoInUrl } from '../utils/urlUtils';
+
+Sentry.init({
+ dsn: 'https://eae192966a8d79509154c65c317a7e5d@o298402.ingest.us.sentry.io/4507254110552064',
+ release: `v${window.AutoUpgradeVariables.module_version}`,
+ sendDefaultPii: false,
+ beforeSend(event) {
+ if (event.type === 'session') {
+ return null;
+ }
+
+ if (event.request?.url) {
+ event.request.url = maskSensitiveInfoInUrl(
+ window.location.href,
+ window.AutoUpgradeVariables.admin_dir
+ );
+ }
+
+ return event;
+ },
+ beforeBreadcrumb(breadcrumb) {
+ if (breadcrumb.data?.url) {
+ breadcrumb.data.url = maskSensitiveInfoInUrl(
+ breadcrumb.data.url,
+ window.AutoUpgradeVariables.admin_dir
+ );
+ }
+
+ if (breadcrumb.data?.from) {
+ breadcrumb.data.from = maskSensitiveInfoInUrl(
+ breadcrumb.data.from,
+ window.AutoUpgradeVariables.admin_dir
+ );
+ }
+
+ if (breadcrumb.data?.to) {
+ breadcrumb.data.to = maskSensitiveInfoInUrl(
+ breadcrumb.data.to,
+ window.AutoUpgradeVariables.admin_dir
+ );
+ }
+
+ return breadcrumb;
+ }
+});
+
+export function sendUserFeedback(
+ message: string,
+ logs: { logs?: string; warnings?: string; errors?: string } = {},
+ feedback: { email?: string; comments?: string } = {},
+ level: SeverityLevel = 'error'
+) {
+ if (logs.logs) {
+ Sentry.getCurrentScope().addAttachment({
+ filename: 'logs.txt',
+ data: logs.logs,
+ contentType: 'text/plain'
+ });
+ }
+
+ if (logs.warnings) {
+ Sentry.getCurrentScope().addAttachment({
+ filename: 'summary_warnings.txt',
+ data: logs.warnings,
+ contentType: 'text/plain'
+ });
+ }
+
+ if (logs.errors) {
+ Sentry.getCurrentScope().addAttachment({
+ filename: 'summary_errors.txt',
+ data: logs.errors,
+ contentType: 'text/plain'
+ });
+ }
+
+ const maskedUrl = maskSensitiveInfoInUrl(
+ window.location.href,
+ window.AutoUpgradeVariables.admin_dir
+ );
+
+ const eventId = Sentry.captureEvent({
+ message,
+ level,
+ tags: {
+ url: maskedUrl
+ }
+ });
+
+ if (feedback.email || feedback.comments) {
+ Sentry.captureFeedback({
+ associatedEventId: eventId,
+ email: feedback.email || '',
+ message: feedback.comments || '',
+ url: maskedUrl
+ });
+ }
+
+ Sentry.getCurrentScope().clearAttachments();
+}
From aef68d82be51910c53ea8da992ff89f6ce4f604a Mon Sep 17 00:00:00 2001
From: Alexis Guyomar
Date: Thu, 5 Dec 2024 10:50:30 +0100
Subject: [PATCH 04/18] feat: add data transparency link
---
classes/Commands/UpdateCommand.php | 6 +++---
.../{DeveloperDocumentation.php => DocumentationLinks.php} | 4 +++-
controllers/admin/self-managed/AbstractPageController.php | 2 ++
views/templates/components/privacy.html.twig | 2 +-
4 files changed, 9 insertions(+), 5 deletions(-)
rename classes/{DeveloperDocumentation.php => DocumentationLinks.php} (62%)
diff --git a/classes/Commands/UpdateCommand.php b/classes/Commands/UpdateCommand.php
index 1e9af570f..fd431264a 100644
--- a/classes/Commands/UpdateCommand.php
+++ b/classes/Commands/UpdateCommand.php
@@ -29,7 +29,7 @@
use Exception;
use InvalidArgumentException;
-use PrestaShop\Module\AutoUpgrade\DeveloperDocumentation;
+use PrestaShop\Module\AutoUpgrade\DocumentationLinks;
use PrestaShop\Module\AutoUpgrade\Parameters\UpgradeConfiguration;
use PrestaShop\Module\AutoUpgrade\Parameters\UpgradeFileNames;
use PrestaShop\Module\AutoUpgrade\Task\ExitCode;
@@ -53,7 +53,7 @@ protected function configure(): void
->setDescription('Update your store.')
->setHelp(
'This command allows you to start the update process. ' .
- 'Advanced users can refer to the ' . DeveloperDocumentation::DEV_DOC_UPGRADE_CLI_URL . ' for further details on available actions'
+ 'Advanced users can refer to the ' . DocumentationLinks::DEV_DOC_UPGRADE_CLI_URL . ' for further details on available actions'
)
->addArgument('admin-dir', InputArgument::REQUIRED, 'The admin directory name.')
->addOption('chain', null, InputOption::VALUE_NONE, 'True by default. Allows you to chain update commands automatically. The command will continue executing subsequent tasks without requiring manual intervention to restart the process.')
@@ -65,7 +65,7 @@ protected function configure(): void
->addOption('regenerate-email-templates', null, InputOption::VALUE_REQUIRED, "Regenerate email templates. If you've customized email templates, your changes will be lost if you activate this option (1 for yes, 0 for no)")
->addOption('disable-all-overrides', null, InputOption::VALUE_REQUIRED, 'Overriding is a way to replace business behaviors (class files and controller files) to target only one method or as many as you need. This option disables all classes & controllers overrides, allowing you to avoid conflicts during and after updates (1 for yes, 0 for no)')
->addOption('config-file-path', null, InputOption::VALUE_REQUIRED, 'Configuration file location for update.')
- ->addOption('action', null, InputOption::VALUE_REQUIRED, 'Advanced users only. Sets the step you want to start from. Only the "' . TaskName::TASK_UPDATE_INITIALIZATION . '" task updates the configuration. (Default: ' . TaskName::TASK_UPDATE_INITIALIZATION . ', see ' . DeveloperDocumentation::DEV_DOC_UPGRADE_CLI_URL . ' for other values available)');
+ ->addOption('action', null, InputOption::VALUE_REQUIRED, 'Advanced users only. Sets the step you want to start from. Only the "' . TaskName::TASK_UPDATE_INITIALIZATION . '" task updates the configuration. (Default: ' . TaskName::TASK_UPDATE_INITIALIZATION . ', see ' . DocumentationLinks::DEV_DOC_UPGRADE_CLI_URL . ' for other values available)');
}
/**
diff --git a/classes/DeveloperDocumentation.php b/classes/DocumentationLinks.php
similarity index 62%
rename from classes/DeveloperDocumentation.php
rename to classes/DocumentationLinks.php
index 07e879aa4..9d7106a39 100644
--- a/classes/DeveloperDocumentation.php
+++ b/classes/DocumentationLinks.php
@@ -2,9 +2,11 @@
namespace PrestaShop\Module\AutoUpgrade;
-class DeveloperDocumentation
+class DocumentationLinks
{
public const DEV_DOC_URL = 'https://devdocs.prestashop-project.org/8';
public const DEV_DOC_UPGRADE_URL = self::DEV_DOC_URL . '/basics/keeping-up-to-date/upgrade-module';
public const DEV_DOC_UPGRADE_CLI_URL = self::DEV_DOC_UPGRADE_URL . '/basics/keeping-up-to-date/upgrade-module/upgrade-cli';
+ public const PRESTASHOP_PROJECT_URL = 'https://www.prestashop-project.org';
+ public const PRESTASHOP_PROJECT_DATA_TRANSPARENCY_URL = self::PRESTASHOP_PROJECT_URL . '/data-transparency';
}
diff --git a/controllers/admin/self-managed/AbstractPageController.php b/controllers/admin/self-managed/AbstractPageController.php
index f6fb6445a..f969d9d96 100644
--- a/controllers/admin/self-managed/AbstractPageController.php
+++ b/controllers/admin/self-managed/AbstractPageController.php
@@ -28,6 +28,7 @@
namespace PrestaShop\Module\AutoUpgrade\Controller;
use PrestaShop\Module\AutoUpgrade\AjaxResponseBuilder;
+use PrestaShop\Module\AutoUpgrade\DocumentationLinks;
use PrestaShop\Module\AutoUpgrade\Twig\PageSelectors;
use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -62,6 +63,7 @@ public function renderPage(string $page, array $params): string
[
'page' => $page,
'ps_version' => $this->getPsVersionClass(),
+ 'data_transparency_link' => DocumentationLinks::PRESTASHOP_PROJECT_DATA_TRANSPARENCY_URL,
],
$pageSelectors::getAllSelectors(),
$params
diff --git a/views/templates/components/privacy.html.twig b/views/templates/components/privacy.html.twig
index d335f1a64..fa715cda2 100644
--- a/views/templates/components/privacy.html.twig
+++ b/views/templates/components/privacy.html.twig
@@ -1,6 +1,6 @@
{{ 'Privacy policy'|trans({}) }}
From 28ba32ec425339a67fe98d72ca79be9598ed2373 Mon Sep 17 00:00:00 2001
From: Alexis Guyomar
Date: Thu, 5 Dec 2024 10:51:01 +0100
Subject: [PATCH 05/18] feat: add reusable template for error report
---
classes/Router/Router.php | 4 ++
classes/Router/Routes.php | 1 +
.../Traits/displayErrorReportDialogTrait.php | 52 +++++++++++++++++++
.../UpdatePageUpdateController.php | 10 ++--
.../dialogs/dialog-error-report.html.twig | 30 ++++++-----
5 files changed, 77 insertions(+), 20 deletions(-)
create mode 100644 classes/Traits/displayErrorReportDialogTrait.php
diff --git a/classes/Router/Router.php b/classes/Router/Router.php
index 9cd180114..c04d26823 100644
--- a/classes/Router/Router.php
+++ b/classes/Router/Router.php
@@ -126,6 +126,10 @@ public function __construct(UpgradeContainer $upgradeContainer)
'controller' => UpdatePageUpdateController::class,
'method' => 'step',
],
+ Routes::UPDATE_STEP_UPDATE_SUBMIT_ERROR_REPORT => [
+ 'controller' => UpdatePageUpdateController::class,
+ 'method' => 'submitErrorReport',
+ ],
Routes::UPDATE_STEP_UPDATE_DOWNLOAD_LOGS => [
'controller' => UpdatePageUpdateController::class,
'method' => 'getDownloadLogsButton',
diff --git a/classes/Router/Routes.php b/classes/Router/Routes.php
index 181058d8b..77de50076 100644
--- a/classes/Router/Routes.php
+++ b/classes/Router/Routes.php
@@ -33,6 +33,7 @@ class Routes
/* step: update */
const UPDATE_PAGE_UPDATE = 'update-page-update';
const UPDATE_STEP_UPDATE = 'update-step-update';
+ const UPDATE_STEP_UPDATE_SUBMIT_ERROR_REPORT = 'update-step-update-submit-error-report';
const UPDATE_STEP_UPDATE_DOWNLOAD_LOGS = 'update-step-update-download-logs';
/* step: post update */
diff --git a/classes/Traits/displayErrorReportDialogTrait.php b/classes/Traits/displayErrorReportDialogTrait.php
new file mode 100644
index 000000000..9a4452275
--- /dev/null
+++ b/classes/Traits/displayErrorReportDialogTrait.php
@@ -0,0 +1,52 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
+ */
+
+namespace PrestaShop\Module\AutoUpgrade\Traits;
+
+use PrestaShop\Module\AutoUpgrade\AjaxResponseBuilder;
+use PrestaShop\Module\AutoUpgrade\DocumentationLinks;
+use PrestaShop\Module\AutoUpgrade\Twig\PageSelectors;
+use Symfony\Component\HttpFoundation\JsonResponse;
+
+trait displayErrorReportDialogTrait
+{
+ public function submitErrorReport(): JsonResponse
+ {
+ return AjaxResponseBuilder::hydrationResponse(
+ PageSelectors::DIALOG_PARENT_ID,
+ $this->getTwig()->render(
+ '@ModuleAutoUpgrade/dialogs/dialog-error-report.html.twig',
+ [
+ 'dialogSize' => 'sm',
+ 'title' => $this->upgradeContainer->getTranslator()->trans('Send error report?'),
+ 'data_transparency_link' => DocumentationLinks::PRESTASHOP_PROJECT_DATA_TRANSPARENCY_URL,
+ ]
+ ),
+ ['addScript' => 'send-error-report-dialog']
+ );
+ }
+}
diff --git a/controllers/admin/self-managed/UpdatePageUpdateController.php b/controllers/admin/self-managed/UpdatePageUpdateController.php
index d3525a0e8..64cd84556 100644
--- a/controllers/admin/self-managed/UpdatePageUpdateController.php
+++ b/controllers/admin/self-managed/UpdatePageUpdateController.php
@@ -35,16 +35,13 @@
use PrestaShop\Module\AutoUpgrade\Twig\UpdateSteps;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
+use PrestaShop\Module\AutoUpgrade\Traits\displayErrorReportDialogTrait;
class UpdatePageUpdateController extends AbstractPageWithStepController
{
- const CURRENT_STEP = UpdateSteps::STEP_UPDATE;
+ use displayErrorReportDialogTrait;
- // TODO: for dev update page comment need to be removed after dev
-// public function index(): RedirectResponse
-// {
-// return $this->redirectTo(Routes::UPDATE_PAGE_BACKUP);
-// }
+ const CURRENT_STEP = UpdateSteps::STEP_UPDATE;
protected function getPageTemplate(): string
{
@@ -98,6 +95,7 @@ protected function getParams(): array
'success_route' => Routes::UPDATE_STEP_POST_UPDATE,
'download_logs_route' => Routes::UPDATE_STEP_UPDATE_DOWNLOAD_LOGS,
'restore_route' => Routes::RESTORE_PAGE_BACKUP_SELECTION,
+ 'submit_error_report_route' => Routes::UPDATE_STEP_UPDATE_SUBMIT_ERROR_REPORT,
'initial_process_action' => TaskName::TASK_UPDATE_INITIALIZATION,
'backup_available' => !empty($backupFinder->getAvailableBackups()),
'download_logs_parent_id' => PageSelectors::DOWNLOAD_LOGS_PARENT_ID,
diff --git a/views/templates/dialogs/dialog-error-report.html.twig b/views/templates/dialogs/dialog-error-report.html.twig
index 5a69f6d1c..53ac6324e 100644
--- a/views/templates/dialogs/dialog-error-report.html.twig
+++ b/views/templates/dialogs/dialog-error-report.html.twig
@@ -6,10 +6,10 @@
{{ 'Help us improve the module by sending us this error report. You can add details in the description if you want to.'|trans({}) }}
- {% if dataPrivacyLink %}
+ {% if data_transparency_link %}
-
- Learn more about your data and your rightslaunch
+
+ {{ 'Learn more about your data and your rights'|trans({}) }}launch
{% endif %}
@@ -17,43 +17,45 @@
{% endblock %}
{% block dialog_extra_content_inner %}
-
+
{% endblock %}
{% block dialog_footer %}
From a03a892725524f5bb1f560574a5d426afcace8ba Mon Sep 17 00:00:00 2001
From: Alexis Guyomar
Date: Thu, 5 Dec 2024 12:17:31 +0100
Subject: [PATCH 06/18] fix: missing using variable
---
_dev/src/ts/dialogs/StartUpdateDialog.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/_dev/src/ts/dialogs/StartUpdateDialog.ts b/_dev/src/ts/dialogs/StartUpdateDialog.ts
index 80437baf2..bf52f03ba 100644
--- a/_dev/src/ts/dialogs/StartUpdateDialog.ts
+++ b/_dev/src/ts/dialogs/StartUpdateDialog.ts
@@ -20,7 +20,7 @@ export default class StartUpdateDialog implements DomLifecycle {
}
get #form(): HTMLFormElement {
- const form = document.forms.namedItem('form-confirm-update');
+ const form = document.forms.namedItem(this.formId);
if (!form) {
throw new Error('Form not found');
}
From a72e5207193d5f3f0eaa4da6c8f2af2430c296a5 Mon Sep 17 00:00:00 2001
From: Alexis Guyomar
Date: Thu, 5 Dec 2024 14:52:55 +0100
Subject: [PATCH 07/18] feat: add send error report dialog script
---
_dev/src/ts/api/sentryApi.ts | 107 +++++++++----------
_dev/src/ts/components/LogsViewer.ts | 3 +
_dev/src/ts/dialogs/SendErrorReportDialog.ts | 79 ++++++++++++++
_dev/src/ts/pages/UpdatePageUpdate.ts | 15 ++-
_dev/src/ts/routing/ScriptHandler.ts | 10 +-
_dev/src/ts/types/sentryApi.ts | 18 ++++
views/templates/steps/update.html.twig | 6 +-
7 files changed, 171 insertions(+), 67 deletions(-)
create mode 100644 _dev/src/ts/dialogs/SendErrorReportDialog.ts
create mode 100644 _dev/src/ts/types/sentryApi.ts
diff --git a/_dev/src/ts/api/sentryApi.ts b/_dev/src/ts/api/sentryApi.ts
index d59720ec4..70cd755ff 100644
--- a/_dev/src/ts/api/sentryApi.ts
+++ b/_dev/src/ts/api/sentryApi.ts
@@ -1,6 +1,9 @@
import * as Sentry from '@sentry/browser';
import { SeverityLevel } from '@sentry/browser';
import { maskSensitiveInfoInUrl } from '../utils/urlUtils';
+import { Feedback, Logs, LogsFields } from '../types/sentryApi';
+
+const adminDir = window.AutoUpgradeVariables.admin_dir;
Sentry.init({
dsn: 'https://eae192966a8d79509154c65c317a7e5d@o298402.ingest.us.sentry.io/4507254110552064',
@@ -12,74 +15,54 @@ Sentry.init({
}
if (event.request?.url) {
- event.request.url = maskSensitiveInfoInUrl(
- window.location.href,
- window.AutoUpgradeVariables.admin_dir
- );
+ event.request.url = maskSensitiveInfoInUrl(window.location.href, adminDir);
}
return event;
},
beforeBreadcrumb(breadcrumb) {
- if (breadcrumb.data?.url) {
- breadcrumb.data.url = maskSensitiveInfoInUrl(
- breadcrumb.data.url,
- window.AutoUpgradeVariables.admin_dir
- );
- }
-
- if (breadcrumb.data?.from) {
- breadcrumb.data.from = maskSensitiveInfoInUrl(
- breadcrumb.data.from,
- window.AutoUpgradeVariables.admin_dir
- );
- }
-
- if (breadcrumb.data?.to) {
- breadcrumb.data.to = maskSensitiveInfoInUrl(
- breadcrumb.data.to,
- window.AutoUpgradeVariables.admin_dir
- );
- }
+ ['url', 'from', 'to'].forEach((key) => {
+ if (breadcrumb.data?.[key]) {
+ breadcrumb.data[key] = maskSensitiveInfoInUrl(breadcrumb.data[key], adminDir);
+ }
+ });
return breadcrumb;
}
});
+/**
+ * Sends enriched user feedback to Sentry with optional logs and metadata.
+ * This function attaches log files, captures a custom event, and optionally sends user feedback with an associated event ID.
+ *
+ * @param {string} message - The message to describe the feedback or error.
+ * @param {Logs} [logs={}] - An object containing optional logs, warnings, and errors to attach.
+ * @param {Feedback} [feedback={}] - An object containing optional user feedback fields such as email and comments.
+ * @param {SeverityLevel} [level='error'] - The severity level of the event (e.g., 'info', 'warning', 'error').
+ */
export function sendUserFeedback(
message: string,
- logs: { logs?: string; warnings?: string; errors?: string } = {},
- feedback: { email?: string; comments?: string } = {},
+ logs: Logs = {},
+ feedback: Feedback = {},
level: SeverityLevel = 'error'
) {
- if (logs.logs) {
- Sentry.getCurrentScope().addAttachment({
- filename: 'logs.txt',
- data: logs.logs,
- contentType: 'text/plain'
- });
- }
+ const attachments: { key: LogsFields; filename: string }[] = [
+ { key: LogsFields.LOGS, filename: 'logs.txt' },
+ { key: LogsFields.WARNINGS, filename: 'summary_warnings.txt' },
+ { key: LogsFields.ERRORS, filename: 'summary_errors.txt' }
+ ];
- if (logs.warnings) {
- Sentry.getCurrentScope().addAttachment({
- filename: 'summary_warnings.txt',
- data: logs.warnings,
- contentType: 'text/plain'
- });
- }
-
- if (logs.errors) {
- Sentry.getCurrentScope().addAttachment({
- filename: 'summary_errors.txt',
- data: logs.errors,
- contentType: 'text/plain'
- });
- }
+ attachments.forEach(({ key, filename }) => {
+ if (logs[key]) {
+ Sentry.getCurrentScope().addAttachment({
+ filename,
+ data: logs[key],
+ contentType: 'text/plain'
+ });
+ }
+ });
- const maskedUrl = maskSensitiveInfoInUrl(
- window.location.href,
- window.AutoUpgradeVariables.admin_dir
- );
+ const maskedUrl = maskSensitiveInfoInUrl(window.location.href, adminDir);
const eventId = Sentry.captureEvent({
message,
@@ -90,12 +73,20 @@ export function sendUserFeedback(
});
if (feedback.email || feedback.comments) {
- Sentry.captureFeedback({
- associatedEventId: eventId,
- email: feedback.email || '',
- message: feedback.comments || '',
- url: maskedUrl
- });
+ Sentry.captureFeedback(
+ {
+ associatedEventId: eventId,
+ email: feedback.email,
+ message: feedback.comments || ''
+ },
+ {
+ captureContext: {
+ tags: {
+ url: maskedUrl
+ }
+ }
+ }
+ );
}
Sentry.getCurrentScope().clearAttachments();
diff --git a/_dev/src/ts/components/LogsViewer.ts b/_dev/src/ts/components/LogsViewer.ts
index 17d98ebf8..34dd05d64 100644
--- a/_dev/src/ts/components/LogsViewer.ts
+++ b/_dev/src/ts/components/LogsViewer.ts
@@ -162,7 +162,10 @@ export default class LogsViewer extends ComponentAbstract implements Destroyable
*/
#createSummary(severity: SeverityClasses, logs: string[]): HTMLDivElement {
const summaryFragment = this.#templateSummary.content.cloneNode(true) as DocumentFragment;
+
const summary = summaryFragment.querySelector('.logs__summary') as HTMLDivElement;
+ summary.setAttribute('data-summary-severity', severity);
+
const summaryScroll = summaryFragment.querySelector('.logs__summary-scroll') as HTMLDivElement;
const title = this.#getSummaryTitle(severity);
diff --git a/_dev/src/ts/dialogs/SendErrorReportDialog.ts b/_dev/src/ts/dialogs/SendErrorReportDialog.ts
new file mode 100644
index 000000000..59e596252
--- /dev/null
+++ b/_dev/src/ts/dialogs/SendErrorReportDialog.ts
@@ -0,0 +1,79 @@
+import DomLifecycle from '../types/DomLifecycle';
+import { sendUserFeedback } from '../api/sentryApi';
+import { Feedback, FeedbackFields, Logs } from '../types/sentryApi';
+
+export default class SendErrorReportDialog implements DomLifecycle {
+ protected readonly formId = 'form-error-feedback';
+
+ public mount = (): void => {
+ this.#form.addEventListener('submit', this.#onSubmit);
+ };
+
+ public beforeDestroy = (): void => {
+ this.#form.removeEventListener('submit', this.#onSubmit);
+ };
+
+ get #form(): HTMLFormElement {
+ const form = document.forms.namedItem(this.formId);
+ if (!form) {
+ throw new Error('Form not found');
+ }
+
+ return form;
+ }
+
+ #onSubmit = (event: SubmitEvent) => {
+ event.preventDefault();
+
+ const logsViewer = document.querySelector(
+ '[data-component="progress-tracker"] [data-component="logs-viewer"]'
+ );
+
+ const logs: Logs = {};
+
+ const logsContent = logsViewer?.querySelector(
+ '[data-slot-component="scroll"] [data-slot-component="list"]'
+ );
+ if (!logsContent) {
+ throw new Error('Logs content to send not found');
+ }
+
+ const message = logsContent.lastChild?.textContent;
+ if (!message) {
+ throw new Error('Message to send not found');
+ }
+
+ if (!logsContent.textContent) {
+ throw new Error('Logs to send not found');
+ }
+ logs.logs = logsContent.textContent;
+
+ const summaryWarningText = logsViewer?.querySelector(
+ '[data-summary-severity="warning"]'
+ )?.textContent;
+ if (summaryWarningText) {
+ logs.warnings = summaryWarningText;
+ }
+
+ const summaryErrorText = logsViewer?.querySelector(
+ '[data-summary-severity="error"]'
+ )?.textContent;
+ if (summaryErrorText) {
+ logs.errors = summaryErrorText;
+ }
+
+ const feedback: Feedback = {};
+
+ const form = event.target as HTMLFormElement;
+ const formData = new FormData(form);
+
+ Object.values(FeedbackFields).forEach((field) => {
+ const value = formData.get(field);
+ if (value && typeof value === 'string') {
+ feedback[field] = value;
+ }
+ });
+
+ sendUserFeedback(message, logs, feedback);
+ };
+}
diff --git a/_dev/src/ts/pages/UpdatePageUpdate.ts b/_dev/src/ts/pages/UpdatePageUpdate.ts
index 0dba59ab3..ac53a0b7c 100644
--- a/_dev/src/ts/pages/UpdatePageUpdate.ts
+++ b/_dev/src/ts/pages/UpdatePageUpdate.ts
@@ -9,6 +9,7 @@ export default class UpdatePageUpdate extends UpdatePage {
#progressTracker: ProgressTracker = new ProgressTracker(this.#progressTrackerContainer);
#restoreAlertForm: null | HTMLFormElement = null;
#restoreButtonForm: null | HTMLFormElement = null;
+ #submitErrorReportForm: null | HTMLFormElement = null;
constructor() {
super();
@@ -32,8 +33,9 @@ export default class UpdatePageUpdate extends UpdatePage {
public beforeDestroy = () => {
this.#progressTracker.beforeDestroy();
- this.#restoreAlertForm?.removeEventListener('submit', this.#handleRestoreSubmit);
- this.#restoreButtonForm?.removeEventListener('submit', this.#handleRestoreSubmit);
+ this.#restoreAlertForm?.removeEventListener('submit', this.#handleSubmit);
+ this.#restoreButtonForm?.removeEventListener('submit', this.#handleSubmit);
+ this.#submitErrorReportForm?.addEventListener('submit', this.#handleSubmit);
};
get #progressTrackerContainer(): HTMLDivElement {
@@ -77,7 +79,7 @@ export default class UpdatePageUpdate extends UpdatePage {
alertContainer.classList.remove('hidden');
this.#restoreAlertForm = document.forms.namedItem('restore-alert');
- this.#restoreAlertForm?.addEventListener('submit', this.#handleRestoreSubmit);
+ this.#restoreAlertForm?.addEventListener('submit', this.#handleSubmit);
};
#displayErrorButtons = () => {
@@ -89,11 +91,14 @@ export default class UpdatePageUpdate extends UpdatePage {
buttonsContainer.classList.remove('hidden');
+ this.#submitErrorReportForm = document.forms.namedItem('submit-error-report');
+ this.#submitErrorReportForm?.addEventListener('submit', this.#handleSubmit);
+
this.#restoreButtonForm = document.forms.namedItem('restore-button');
- this.#restoreButtonForm?.addEventListener('submit', this.#handleRestoreSubmit);
+ this.#restoreButtonForm?.addEventListener('submit', this.#handleSubmit);
};
- #handleRestoreSubmit = async (event: SubmitEvent) => {
+ #handleSubmit = async (event: SubmitEvent) => {
event.preventDefault();
const form = event.target as HTMLFormElement;
diff --git a/_dev/src/ts/routing/ScriptHandler.ts b/_dev/src/ts/routing/ScriptHandler.ts
index 7c4caaf66..a4f5a717b 100644
--- a/_dev/src/ts/routing/ScriptHandler.ts
+++ b/_dev/src/ts/routing/ScriptHandler.ts
@@ -4,10 +4,13 @@ import UpdatePageUpdateOptions from '../pages/UpdatePageUpdateOptions';
import UpdatePageBackup from '../pages/UpdatePageBackup';
import UpdatePageUpdate from '../pages/UpdatePageUpdate';
import UpdatePagePostUpdate from '../pages/UpdatePagePostUpdate';
+
+import StartUpdateDialog from '../dialogs/StartUpdateDialog';
+import SendErrorReportDialog from '../dialogs/SendErrorReportDialog';
+
import DomLifecycle from '../types/DomLifecycle';
import { RoutesMatching } from '../types/scriptHandlerTypes';
import { routeHandler } from '../autoUpgrade';
-import StartUpdateDialog from '../dialogs/StartUpdateDialog';
export default class ScriptHandler {
#currentScript: DomLifecycle | undefined;
@@ -25,7 +28,8 @@ export default class ScriptHandler {
'update-page-update': UpdatePageUpdate,
'update-page-post-update': UpdatePagePostUpdate,
- 'start-update-dialog': StartUpdateDialog
+ 'start-update-dialog': StartUpdateDialog,
+ 'send-error-report-dialog': SendErrorReportDialog
};
/**
@@ -42,7 +46,7 @@ export default class ScriptHandler {
/**
* @public
- * @param {string} routeName - The name of the route to load his associated script.
+ * @param {string} scriptID - The name of the route to load his associated script.
* @returns void
* @description Loads and mounts the page script associated with the specified route name.
*/
diff --git a/_dev/src/ts/types/sentryApi.ts b/_dev/src/ts/types/sentryApi.ts
new file mode 100644
index 000000000..2283b84ea
--- /dev/null
+++ b/_dev/src/ts/types/sentryApi.ts
@@ -0,0 +1,18 @@
+enum LogsFields {
+ 'LOGS' = 'logs',
+ 'WARNINGS' = 'warnings',
+ 'ERRORS' = 'errors'
+}
+
+enum FeedbackFields {
+ 'EMAIL' = 'email',
+ 'COMMENTS' = 'comments'
+}
+
+export { LogsFields, FeedbackFields };
+
+type Logs = Partial>;
+
+type Feedback = Partial>;
+
+export type { Logs, Feedback };
diff --git a/views/templates/steps/update.html.twig b/views/templates/steps/update.html.twig
index c48caf34a..c645ba605 100644
--- a/views/templates/steps/update.html.twig
+++ b/views/templates/steps/update.html.twig
@@ -27,7 +27,11 @@
{% block buttons %}
{% endblock %}
From 0901ef4160e235916735fa4380e320a2267326b7 Mon Sep 17 00:00:00 2001
From: Alexis Guyomar
Date: Wed, 11 Dec 2024 07:20:39 +0100
Subject: [PATCH 17/18] fix: feedback from review
---
_dev/src/ts/dialogs/SendErrorReportDialog.ts | 8 ++------
_dev/src/ts/routing/ScriptHandler.ts | 2 +-
views/templates/dialogs/dialog-error-report.html.twig | 4 ++--
3 files changed, 5 insertions(+), 9 deletions(-)
diff --git a/_dev/src/ts/dialogs/SendErrorReportDialog.ts b/_dev/src/ts/dialogs/SendErrorReportDialog.ts
index 59e596252..ebde6ded7 100644
--- a/_dev/src/ts/dialogs/SendErrorReportDialog.ts
+++ b/_dev/src/ts/dialogs/SendErrorReportDialog.ts
@@ -25,15 +25,11 @@ export default class SendErrorReportDialog implements DomLifecycle {
#onSubmit = (event: SubmitEvent) => {
event.preventDefault();
- const logsViewer = document.querySelector(
- '[data-component="progress-tracker"] [data-component="logs-viewer"]'
- );
+ const logsViewer = document.querySelector('[data-component="logs-viewer"]');
const logs: Logs = {};
- const logsContent = logsViewer?.querySelector(
- '[data-slot-component="scroll"] [data-slot-component="list"]'
- );
+ const logsContent = logsViewer?.querySelector('[data-slot-component="list"]');
if (!logsContent) {
throw new Error('Logs content to send not found');
}
diff --git a/_dev/src/ts/routing/ScriptHandler.ts b/_dev/src/ts/routing/ScriptHandler.ts
index a4f5a717b..f93758b07 100644
--- a/_dev/src/ts/routing/ScriptHandler.ts
+++ b/_dev/src/ts/routing/ScriptHandler.ts
@@ -46,7 +46,7 @@ export default class ScriptHandler {
/**
* @public
- * @param {string} scriptID - The name of the route to load his associated script.
+ * @param {string} scriptID - The ID of the route to load his associated script.
* @returns void
* @description Loads and mounts the page script associated with the specified route name.
*/
diff --git a/views/templates/dialogs/dialog-error-report.html.twig b/views/templates/dialogs/dialog-error-report.html.twig
index ad1fa04c4..6dd6807fa 100644
--- a/views/templates/dialogs/dialog-error-report.html.twig
+++ b/views/templates/dialogs/dialog-error-report.html.twig
@@ -20,7 +20,7 @@