diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fdebf90f..020fdc6ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -294,4 +294,42 @@ ## Others -* Chrome-Launcher: Open all example.com URLs with CDP and multi-tab on. https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/560 \ No newline at end of file +* Chrome-Launcher: Open all example.com URLs with CDP and multi-tab on. https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/560 + + +#v0.7.0 + +## Extension +* Feature: Export report in an HTML file https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/545 +* Feature: Show exempted cookies in extension https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/522 +* Feature: Add i18n module https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/575 +* Feature: Accept GDPR banner consent. https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/372 +* Fix: Left over references and make some more space in the cookie table https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/584 +* Fix: Clear cookie preview area https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/568 +* Refactor: Update table storage and prefix icon type https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/546 +* Refactor: Extension UI and Service Worker. https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/519 +* Refactor: Rename column cookie affected column to issues https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/580 +* Refactor: Add disabled state to the Button component https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/585 +* Refactor: Increase test coverage for extension package https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/520 +* Refactor: Restructure cookies landing page https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/528 +* Enhancement: Add information on how to verify if a user is part of 1% experimental group https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/543 +* Enhancement:Update extension icons https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/583 +* Enhancement: Enlarge extension icon https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/598 +* Enhancement : Add blocking status column in the CSV export from the table in extension https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/591 +* Enhancement: Add context provider to sidebar https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/498 +* Miscellaneous UI updates https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/599 +* Enhancement: Navigate to `Settings` page from `Blocked Cookies` section https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/603 +* Enhancement: Use the warning icon on the cookie issues panel https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/604 +* Refactor: Update cookies section on the landing page https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/615 +* Fix: Context invalidated for sites which do not send request after the site is loaded. https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/611 + +## CLI +* Feature: Add option to pass a port for development server https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/579 +* Enhancement: Move download report button in cli-dashboard to the sticky menu bar https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/595 +* Enhancement: CLI, replace "Cookies with issues" with "Cookie issues" in the sidebar. https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/596 +* Enhancement: CLI updates https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/602 +* Fix: CLI dashboard showing multiple cookie entries for same cookies https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/597 +* Fix: CLI dashboard blocking reason. https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/618 + +## Others +* Add demo RWS for command line launcher https://github.com/GoogleChromeLabs/ps-analysis-tool/pull/578 \ No newline at end of file diff --git a/assets/data/open-cookie-database.json b/assets/data/open-cookie-database.json index d14760b73..77270694d 100644 --- a/assets/data/open-cookie-database.json +++ b/assets/data/open-cookie-database.json @@ -3013,6 +3013,19 @@ "wildcard": "0" } ], + "esctx-*": [ + { + "platform": "Microsoft", + "category": "Functional", + "name": "esctx-*", + "domain": ".login.microsoftonline.com", + "description": "This cookie is set by Microsoft for secure authentication of the users' login details", + "retention": "session", + "dataController": "Microsoft", + "gdprUrl": "https://account.microsoft.com/privacy", + "wildcard": "1" + } + ], "ASP.NET_SessionId": [ { "platform": "ASP.net", @@ -3852,7 +3865,7 @@ "description": "Hotjar cookie. This cookie is set when the customer first lands on a page with the Hotjar script. It is used to persist the random user ID, unique to that site on the browser. This ensures that behavior in subsequent visits to the same site will be attributed to the same user ID.", "retention": "365 days", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -3865,7 +3878,7 @@ "description": "Hotjar cookie. This session cookie is set to let Hotjar know whether that visitor is included in the sample which is used to generate funnels.", "retention": "365 days", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -3878,7 +3891,7 @@ "description": "Hotjar cookie. This cookie is set once a visitor interacts with a Survey invitation modal popup. It is used to ensure that the same invite does not re-appear if it has already been shown.", "retention": "365 days", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -3891,7 +3904,7 @@ "description": "Hotjar cookie. This cookie is set once a visitor completes a poll using the Feedback Poll widget. It is used to ensure that the same poll does not re-appear if it has already been filled in.", "retention": "365 days", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -3904,7 +3917,7 @@ "description": "Hotjar cookie. This cookie is set once a visitor minimizes a Feedback Poll widget. It is used to ensure that the widget stays minimizes when the visitor navigates through your site.", "retention": "365 days", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -3917,7 +3930,7 @@ "description": "Hotjar cookie. This cookie is set once a visitor submits their information in the Recruit User Testers widget. It is used to ensure that the same form does not re-appear if it has already been filled in.", "retention": "365 days", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -3930,7 +3943,7 @@ "description": "Hotjar cookie. This cookie is set once a visitor minimizes a Recruit User Testers widget. It is used to ensure that the widget stays minimizes when the visitor navigates through your site.", "retention": "365 days", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -3943,7 +3956,7 @@ "description": "This cookie is set when a visitor minimizes or completes Incoming Feedback. This is done so that the Incoming Feedback will load as minimized immediately if they navigate to another page where it is set to show.", "retention": "365 days", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -3956,7 +3969,7 @@ "description": "When the Hotjar script executes we try to determine the most generic cookie path we should use, instead of the page hostname. This is done so that cookies can be shared across subdomains (where applicable). To determine this, we try to store the _hjTLDTest cookie for different URL substring alternatives until it fails. After this check, the cookie is removed.", "retention": "session", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -3969,7 +3982,7 @@ "description": "User Attributes sent through the Hotjar Identify API are cached for the duration of the session in order to know when an attribute has changed and needs to be updated.", "retention": "session", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -3982,7 +3995,7 @@ "description": "This cookie stores User Attributes which are sent through the Hotjar Identify API, whenever the user is not in the sample. These attributes will only be saved if the user interacts with a Hotjar Feedback tool.", "retention": "session", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -3995,7 +4008,7 @@ "description": "This cookie is used to check if the Hotjar Tracking Script can use local storage. If it can, a value of 1 is set in this cookie. The data stored in_hjLocalStorageTest has no expiration time, but it is deleted immediately after creating it so the expected storage time is under 100ms.", "retention": "", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -4008,7 +4021,7 @@ "description": "This cookie is set for logged in users of Hotjar, who have Admin Team Member permissions. It is used during pricing experiments to show the Admin consistent pricing across the site.", "retention": "session", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -4021,7 +4034,7 @@ "description": "The cookie is set so Hotjar can track the beginning of the user's journey for a total session count. It does not contain any identifiable information.", "retention": "30 minutes", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -4034,7 +4047,7 @@ "description": "The cookie is set so Hotjar can track the beginning of the user's journey for a total session count. It does not contain any identifiable information.", "retention": "30 minutes", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -4047,7 +4060,7 @@ "description": "This cookie is set to let Hotjar know whether that visitor is included in the data sampling defined by your site's page view limit.", "retention": "30 minutes", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -4060,7 +4073,7 @@ "description": "This cookie is set to let Hotjar know whether that visitor is included in the data sampling defined by your site's daily session limit", "retention": "30 minutes", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "1" } ], @@ -4073,7 +4086,7 @@ "description": "A cookie that holds the current session data. This ensues that subsequent requests within the session window will be attributed to the same Hotjar session.", "retention": "30 minutes", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "1" } ], @@ -4086,7 +4099,7 @@ "description": "Hotjar cookie that is set when a user first lands on a page with the Hotjar script. It is used to persist the Hotjar User ID, unique to that site on the browser. This ensures that behavior in subsequent visits to the same site will be attributed to the same user ID.", "retention": "365 days", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "1" } ], @@ -4099,7 +4112,7 @@ "description": "Causes Hotjar to stop collecting data if a session becomes too large. This is determined automatically by a signal from the WebSocket server if the session size exceeds the limit.", "retention": "session", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -4112,7 +4125,7 @@ "description": "If present, this cookie will be set to 1 for the duration of a user’s session, if Hotjar rejected the session from connecting to our WebSocket due to server overload. This cookie is only applied in extremely rare situations to prevent severe performance issues.", "retention": "session", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -4125,7 +4138,7 @@ "description": "A cookie that is set when a session/recording is reconnected to Hotjar servers after a break in connection.", "retention": "session", "dataController": "Hotjar", - "gdprUrl": "https://www.hotjar.com/legal/compliance/opt-out", + "gdprUrl": "https://www.hotjar.com/legal/policies/privacy/", "wildcard": "0" } ], @@ -5564,7 +5577,7 @@ "platform": "ComScore", "category": "Marketing", "name": "UIDR", - "domain": "scorecardresearch.com", + "domain": ".scorecardresearch.com", "description": "Collects information of the user and his/her movement, such as timestamp for visits, most recently loaded pages and IP address. The data is used by the marketing research network, Scorecard Research, to analyse traffic patterns and carry out surveys to help their clients better understand the customer's preferences.", "retention": "2 years", "dataController": "ComScore", @@ -5585,6 +5598,32 @@ "wildcard": "0" } ], + "PID": [ + { + "platform": "ComScore", + "category": "Marketing", + "name": "PID", + "domain": ".scorecardresearch.com", + "description": "Collects a code that identifies the specific website or advertiser participating in the ScorecardResearch data collection program.", + "retention": "1 year", + "dataController": "ComScore", + "gdprUrl": "https://www.comscore.com/About/Privacy-Policy", + "wildcard": "0" + } + ], + "XID": [ + { + "platform": "ComScore", + "category": "Marketing", + "name": "XID", + "domain": ".scorecardresearch.com", + "description": "Collects a unique identifier assigned to a device (computer, phone, tablet) to track the user across different websites.", + "retention": "1 year", + "dataController": "ComScore", + "gdprUrl": "https://www.comscore.com/About/Privacy-Policy", + "wildcard": "0" + } + ], "SEUNCY": [ { "platform": "semasio.net", @@ -5923,27 +5962,79 @@ ], "st_csd": [ { - "platform": "seedtag.com", + "platform": "Seedtag", "category": "Marketing", "name": "st_csd", "domain": "seedtag.com", "description": "Date of the last cookie-syn", "retention": "1 year", "dataController": "seedtag.com", - "gdprUrl": "", + "gdprUrl": "https://www.seedtag.com/privacy/", "wildcard": "0" } ], "st_cs": [ { - "platform": "seedtag.com", + "platform": "Seedtag", "category": "Marketing", "name": "st_cs", "domain": "seedtag.com", "description": "Unique identifiers of DSPs", "retention": "1 year", "dataController": "seedtag.com", - "gdprUrl": "", + "gdprUrl": "https://www.seedtag.com/privacy/", + "wildcard": "0" + } + ], + "st_uid": [ + { + "platform": "Seedtag", + "category": "Marketing", + "name": "st_uid", + "domain": "seedtag.com", + "description": "This cookie is used to store randomly generated unique browser identifier", + "retention": "1 year", + "dataController": "seedtag.com", + "gdprUrl": "https://www.seedtag.com/privacy/", + "wildcard": "0" + } + ], + "st_cnt": [ + { + "platform": "Seedtag", + "category": "Marketing", + "name": "st_cnt", + "domain": "seedtag.com", + "description": "This cookie is used to store low precision geolocation (Country, City)", + "retention": "1 year", + "dataController": "seedtag.com", + "gdprUrl": "https://www.seedtag.com/privacy/", + "wildcard": "0" + } + ], + "st_chc": [ + { + "platform": "Seedtag", + "category": "Marketing", + "name": "st_chc", + "domain": "seedtag.com", + "description": "This cookie is used to store Cookie-sync", + "retention": "1 year", + "dataController": "seedtag.com", + "gdprUrl": "https://www.seedtag.com/privacy/", + "wildcard": "0" + } + ], + "st_ssp": [ + { + "platform": "Seedtag", + "category": "Marketing", + "name": "st_ssp", + "domain": "seedtag.com", + "description": "This cookie is used to store low precision geolocation", + "retention": "1 year", + "dataController": "seedtag.com", + "gdprUrl": "https://www.seedtag.com/privacy/", "wildcard": "0" } ], @@ -6266,7 +6357,46 @@ "description": "This cookie is used to identify the visitor and optimize ad-relevance by collecting visitor data from multiple websites – this exchange of visitor data is normally provided by a third-party data-center or ad-exchange.", "retention": "3 months", "dataController": "TripleLift", - "gdprUrl": "", + "gdprUrl": "https://triplelift.com/advertising-technology-platform-cookie-notice/", + "wildcard": "0" + } + ], + "tluidp": [ + { + "platform": "TripleLift", + "category": "Marketing", + "name": "tluidp", + "domain": "3lift.com", + "description": "This cookie is used to identify the visitor and optimize ad-relevance by collecting visitor data from multiple websites with – this exchange of visitor data is normally provided by a third-party data-center or ad-exchange.", + "retention": "3 months", + "dataController": "TripleLift", + "gdprUrl": "https://triplelift.com/advertising-technology-platform-cookie-notice/", + "wildcard": "0" + } + ], + "optout": [ + { + "platform": "TripleLift", + "category": "Marketing", + "name": "optout", + "domain": "3lift.com", + "description": "This cookie is used to determine whether the visitor has accepted the cookie consent box.", + "retention": "5 years", + "dataController": "TripleLift", + "gdprUrl": "https://triplelift.com/advertising-technology-platform-cookie-notice/", + "wildcard": "0" + } + ], + "sync": [ + { + "platform": "TripleLift", + "category": "Marketing", + "name": "sync", + "domain": "3lift.com", + "description": "This cookie is used in order to transact in digital advertising, TripleLift exchanges (or syncs) identifiers with other companies. This cookie keeps track of which companies have recently been synced in order to avoid syncing with the same companies repetitively.", + "retention": "3 months", + "dataController": "TripleLift", + "gdprUrl": "https://triplelift.com/advertising-technology-platform-cookie-notice/", "wildcard": "0" } ], @@ -6276,7 +6406,7 @@ "category": "Marketing", "name": "t_gid", "domain": "taboola.com", - "description": "This cookie gives a user who interacts with Taboola Widget a User ID allowing us to target advertisements and content to this specific user ID.", + "description": "This Partitioned cookie gives a user who interacts with Taboola Widget a User ID allowing us to target advertisements and content to this specific user ID.", "retention": "13 months", "dataController": "taboola.com", "gdprUrl": "", @@ -7091,14 +7221,235 @@ ], "everest_g_v2": [ { - "platform": "Everest Technologies", + "platform": "Adobe Advertising", "category": "Marketing", "name": "everest_g_v2", "domain": "everesttech.net", - "description": "Created after a user initially clicks a client's ad, and used to map the current and subsequent clicks with other events on the client's website", + "description": "This cookie stores the browser and surfer ID.Created after a user initially clicks a client's ad, and used to map the current and subsequent clicks with other events on the client's website", "retention": "2 years", - "dataController": "Everest Technologies", - "gdprUrl": "", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "everest_session_v2": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "everest_session_v2", + "domain": "everesttech.net", + "description": "This cookie stores the session ID", + "retention": "session", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "ev_tm": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "ev_tm", + "domain": "everesttech.net", + "description": "This cookie stores the Adobe Advertising DSP (Demand Side Platform) ID. \tA third-party cookie that stores the DSP ID that corresponds to the surfer ID in the everest_g_v2 cookie", + "retention": "2 years", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "_tmae": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "_tmae", + "domain": "everesttech.net", + "description": "This cookie stores Encoded IDs and time stamps for ad engagements using Adobe Advertising DSP tracking.A third-party cookie that stores user engagements with ads, such as 'last seen ad xyz123 on June 30, 2016'", + "retention": "1 year", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "_lcc": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "_lcc", + "domain": "everesttech.net", + "description": "This cookie stores IDs and time stamps (in the format yyyymmdd) of display clicks. It is a third-party cookie used to determine if a click event on a display ad applies to an Adobe Analytics hit", + "retention": "15 minutes", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "ev_sync_ax": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "ev_sync_ax", + "domain": "everesttech.net", + "description": "This cookie stores The date when synchronization is performed, in the format yyyymmdd. A third-party, ad exchange-specific cookie that syncs the Adobe Advertising surfer ID with the partner ad exchange. It's created for new surfers and sends a synchronization request when it's expired.", + "retention": "1 year", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "ev_sync_bk": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "ev_sync_bk", + "domain": "everesttech.net", + "description": "This cookie stores The date when synchronization is performed, in the format yyyymmdd. A third-party, ad exchange-specific cookie that syncs the Adobe Advertising surfer ID with the partner ad exchange. It's created for new surfers and sends a synchronization request when it's expired.", + "retention": "1 year", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "ev_sync_dd": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "ev_sync_dd", + "domain": "everesttech.net", + "description": "This cookie stores The date when synchronization is performed, in the format yyyymmdd. A third-party, ad exchange-specific cookie that syncs the Adobe Advertising surfer ID with the partner ad exchange. It's created for new surfers and sends a synchronization request when it's expired.", + "retention": "1 year", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "ev_sync_fs": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "ev_sync_fs", + "domain": "everesttech.net", + "description": "This cookie stores The date when synchronization is performed, in the format yyyymmdd. A third-party, ad exchange-specific cookie that syncs the Adobe Advertising surfer ID with the partner ad exchange. It's created for new surfers and sends a synchronization request when it's expired.", + "retention": "1 year", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "ev_sync_ix": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "ev_sync_ix", + "domain": "everesttech.net", + "description": "This cookie stores The date when synchronization is performed, in the format yyyymmdd. A third-party, ad exchange-specific cookie that syncs the Adobe Advertising surfer ID with the partner ad exchange. It's created for new surfers and sends a synchronization request when it's expired.", + "retention": "1 year", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "ev_sync_nx": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "ev_sync_nx", + "domain": "everesttech.net", + "description": "This cookie stores The date when synchronization is performed, in the format yyyymmdd. A third-party, ad exchange-specific cookie that syncs the Adobe Advertising surfer ID with the partner ad exchange. It's created for new surfers and sends a synchronization request when it's expired.", + "retention": "1 year", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "ev_sync_ox": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "ev_sync_ox", + "domain": "everesttech.net", + "description": "This cookie stores The date when synchronization is performed, in the format yyyymmdd. A third-party, ad exchange-specific cookie that syncs the Adobe Advertising surfer ID with the partner ad exchange. It's created for new surfers and sends a synchronization request when it's expired.", + "retention": "1 year", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "ev_sync_pm": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "ev_sync_pm", + "domain": "everesttech.net", + "description": "This cookie stores The date when synchronization is performed, in the format yyyymmdd. A third-party, ad exchange-specific cookie that syncs the Adobe Advertising surfer ID with the partner ad exchange. It's created for new surfers and sends a synchronization request when it's expired.", + "retention": "1 year", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "ev_sync_rc": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "ev_sync_rc", + "domain": "everesttech.net", + "description": "This cookie stores The date when synchronization is performed, in the format yyyymmdd. A third-party, ad exchange-specific cookie that syncs the Adobe Advertising surfer ID with the partner ad exchange. It's created for new surfers and sends a synchronization request when it's expired.", + "retention": "1 year", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "ev_sync_tm": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "ev_sync_tm", + "domain": "everesttech.net", + "description": "This cookie stores The date when synchronization is performed, in the format yyyymmdd. A third-party, ad exchange-specific cookie that syncs the Adobe Advertising surfer ID with the partner ad exchange. It's created for new surfers and sends a synchronization request when it's expired.", + "retention": "1 year", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "ev_sync_yh": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "ev_sync_yh", + "domain": "everesttech.net", + "description": "This cookie stores the date when synchronization is performed, in the format yyyymmdd. A third-party, ad exchange-specific cookie that syncs the Adobe Advertising surfer ID with the partner ad exchange. It's created for new surfers and sends a synchronization request when it's expired.", + "retention": "1 year", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "adcloud": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "adcloud", + "domain": "", + "description": "This cookie stores The timestamps of the surfer's last visit to the advertiser’s website and the surfer's last search click, and the ef_id that was created when the user clicked an ad", + "retention": "1 year", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], + "id_adcloud": [ + { + "platform": "Adobe Advertising", + "category": "Marketing", + "name": "id_adcloud", + "domain": "", + "description": "This cookie stores the surfer ID", + "retention": "91 days", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", "wildcard": "0" } ], @@ -7280,7 +7631,59 @@ "description": "Registers data on visitors from multiple visits and on multiple websites. This information is used to measure the efficiency of advertisement on websites.", "retention": "10 years", "dataController": "Zeotap", - "gdprUrl": "https://www.zeotap.com/privacy_policy", + "gdprUrl": "https://zeotap.com/product-privacy-policy/", + "wildcard": "0" + } + ], + "zsc": [ + { + "platform": "Zeotap", + "category": "Marketing", + "name": "zsc", + "domain": "zeotap.com", + "description": "Frequency capping for cookie syncing", + "retention": "1 day", + "dataController": "Zeotap", + "gdprUrl": "https://zeotap.com/product-privacy-policy/", + "wildcard": "0" + } + ], + "zi": [ + { + "platform": "Zeotap", + "category": "Marketing", + "name": "zi", + "domain": "zeotap.com", + "description": "User Identification", + "retention": "1 year", + "dataController": "Zeotap", + "gdprUrl": "https://zeotap.com/product-privacy-policy/", + "wildcard": "0" + } + ], + "idp": [ + { + "platform": "Zeotap", + "category": "Marketing", + "name": "idp", + "domain": "zeotap.com", + "description": "User Identification", + "retention": "1 year", + "dataController": "Zeotap", + "gdprUrl": "https://zeotap.com/product-privacy-policy/", + "wildcard": "0" + } + ], + "zuc": [ + { + "platform": "Zeotap", + "category": "Marketing", + "name": "zuc", + "domain": "zeotap.com", + "description": "User Identification", + "retention": "1 year", + "dataController": "Zeotap", + "gdprUrl": "https://zeotap.com/product-privacy-policy/", "wildcard": "0" } ], @@ -7960,6 +8363,19 @@ "wildcard": "0" } ], + "login_redirect": [ + { + "platform": "Magento", + "category": "Functional", + "name": "login_redirect", + "domain": "", + "description": "Preserves the destination page that was loading before the customer was directed to log in.", + "retention": "session", + "dataController": "Adobe", + "gdprUrl": "https://www.adobe.com/privacy.html", + "wildcard": "0" + } + ], "dsps:*": [ { "platform": "PowerLinks Media Limited", @@ -9269,6 +9685,45 @@ "wildcard": "0" } ], + "hubspotapi": [ + { + "platform": "Hubspot", + "category": "Marketing", + "name": "hubspotapi", + "domain": "hubspot.com", + "description": "This cookie allows the user to access the app with the correct permissions.", + "retention": "7 days", + "dataController": "HubSpot", + "gdprUrl": "https://legal.hubspot.com/privacy-policy", + "wildcard": "0" + } + ], + "hubspotapi-prefs": [ + { + "platform": "Hubspot", + "category": "Functional", + "name": "hubspotapi-prefs", + "domain": "hubspot.com", + "description": "This is used with the hubspotapi cookie to remember whether the user checked the 'remember me' box (controls the expiration of the main cookie's authentication).", + "retention": "1 Year", + "dataController": "HubSpot", + "gdprUrl": "https://legal.hubspot.com/privacy-policy", + "wildcard": "0" + } + ], + "hubspotapi-csrf": [ + { + "platform": "Hubspot", + "category": "Functional", + "name": "hubspotapi-csrf", + "domain": "hubspot.com", + "description": "This is used for CSRF prevention - preventing third party websites from accessing your data. Expires after a year.", + "retention": "1 year", + "dataController": "HubSpot", + "gdprUrl": "https://legal.hubspot.com/privacy-policy", + "wildcard": "0" + } + ], "vuid": [ { "platform": "Vimeo", @@ -11657,7 +12112,7 @@ "description": "Unique identifier for the BlueConic profile.", "retention": "1 year", "dataController": "Blueconic.com", - "gdprUrl": "https://www.blueconic.com/privacy-policy/", + "gdprUrl": "https://www.blueconic.com/privacy-policy", "wildcard": "0" } ], @@ -11670,7 +12125,7 @@ "description": "Temporary unique identifier for the BlueConic profile, removed after BCSessionID is created.", "retention": "10 minutes", "dataController": "Blueconic.com", - "gdprUrl": "https://www.blueconic.com/privacy-policy/", + "gdprUrl": "https://www.blueconic.com/privacy-policy", "wildcard": "0" } ], @@ -11683,7 +12138,7 @@ "description": "Opt-in level (PERSONAL|ANONYMOUS|DO_NOT_TRACK)", "retention": "1 year", "dataController": "Blueconic.com", - "gdprUrl": "https://www.blueconic.com/privacy-policy/", + "gdprUrl": "https://www.blueconic.com/privacy-policy", "wildcard": "0" } ], @@ -11696,7 +12151,7 @@ "description": "Stores a custom bcChannelIdentifier as referrer. For these channels the actual referrer points to the website and not the overrule. The overrule would be lost if not stored in this cookie.", "retention": "1 year", "dataController": "Blueconic.com", - "gdprUrl": "https://www.blueconic.com/privacy-policy/", + "gdprUrl": "https://www.blueconic.com/privacy-policy", "wildcard": "0" } ], @@ -11709,7 +12164,7 @@ "description": "Used to store the identifiers of BlueConic Objectives that were explicitly refused.", "retention": "1 year", "dataController": "Blueconic.com", - "gdprUrl": "https://www.blueconic.com/privacy-policy/", + "gdprUrl": "https://www.blueconic.com/privacy-policy", "wildcard": "0" } ], @@ -11722,7 +12177,7 @@ "description": "Used to store requests that are sent to BlueConic, but haven't returned yet. On the next page view, if BCRevision still contains values, those requests are sent again, to prevent data loss. This information is now stored in localStorage; when this fails, the cookie solution is used as fallback.", "retention": "1 year", "dataController": "Blueconic.com", - "gdprUrl": "https://www.blueconic.com/privacy-policy/", + "gdprUrl": "https://www.blueconic.com/privacy-policy", "wildcard": "0" } ], @@ -11735,7 +12190,7 @@ "description": "Used for tracking the channel of an external tracker.", "retention": "10 seconds", "dataController": "Blueconic.com", - "gdprUrl": "https://www.blueconic.com/privacy-policy/", + "gdprUrl": "https://www.blueconic.com/privacy-policy", "wildcard": "0" } ], @@ -11748,7 +12203,7 @@ "description": "Gathers information on the user’s behavior, preferences and other personal data, which is sent to a third-party marketing and analysis service, for optimization of the website’s advertisement, analysis and general traffic.", "retention": "1 year", "dataController": "Blueconic.com", - "gdprUrl": "https://www.blueconic.com/privacy-policy/", + "gdprUrl": "https://www.blueconic.com/privacy-policy", "wildcard": "0" } ], @@ -17998,7 +18453,7 @@ "category": "Functional", "name": "__pid", "domain": "", - "description": "This cookie store the domain received on the frontend is used as a domain for other cookies (incl. __utp, __idr, __tae) Example value: .piano.io", + "description": "This cookie stores the domain received on the frontend is used as a domain for other cookies (incl. __utp, __idr, __tae) Example value: .piano.io", "retention": "30 days", "dataController": "Piano", "gdprUrl": "https://piano.io/privacy-policy/", @@ -18076,7 +18531,7 @@ "category": "Analytics", "name": "__pvi", "domain": "", - "description": "This cookie store data about the last visit to the site including the AID, lastTrackedVisitId, domain and time of the visit. Used for reporting only.", + "description": "This cookie stores data about the last visit to the site including the AID, lastTrackedVisitId, domain and time of the visit. Used for reporting only.", "retention": "1 day", "dataController": "Piano", "gdprUrl": "https://piano.io/privacy-policy/", @@ -18089,7 +18544,7 @@ "category": "Analytics", "name": "__pat", "domain": "", - "description": "This cookie store difference between the client’s application time zone and UTC. At midnight, (application's local time), the previous visit is expired and a new one is created. The cookie is used for calculation.", + "description": "This cookie stores difference between the client’s application time zone and UTC. At midnight, (application's local time), the previous visit is expired and a new one is created. The cookie is used for calculation.", "retention": "30 days", "dataController": "Piano", "gdprUrl": "https://piano.io/privacy-policy/", @@ -18102,7 +18557,7 @@ "category": "Functional", "name": "__pnahc", "domain": "", - "description": "This cookie store the result of previous Adblock detection, removes false-positive AdBlock detection clauses.", + "description": "This cookie stores the result of previous Adblock detection, removes false-positive AdBlock detection clauses.", "retention": "90 days", "dataController": "Piano", "gdprUrl": "https://piano.io/privacy-policy/", @@ -18115,7 +18570,7 @@ "category": "Functional", "name": "LANG", "domain": "tinypass.com", - "description": "This cookie store the selected locale", + "description": "This cookie stores the selected locale", "retention": "1500 days", "dataController": "Piano", "gdprUrl": "https://piano.io/privacy-policy/", @@ -18128,7 +18583,7 @@ "category": "Functional", "name": "LANG_CHANGED", "domain": "tinypass.com", - "description": "This cookie store the temporarily selected locale (e.g. impersonation in Admin dashboard).", + "description": "This cookie stores the temporarily selected locale (e.g. impersonation in Admin dashboard).", "retention": "1 day", "dataController": "Piano", "gdprUrl": "https://piano.io/privacy-policy/", @@ -18732,5 +19187,304 @@ "gdprUrl": "https://www.emetriq.com/datenschutz/", "wildcard": "0" } + ], + "_pangle": [ + { + "platform": "Pangle", + "category": "Marketing", + "name": "_pangle", + "domain": "analytics.pangle-ads.com", + "description": "This cookie is to measure and improve the performance of your advertising campaigns and to personalize the user's ad experiences delivered by the Pangle ad network.", + "retention": "13 months", + "dataController": "Pangle", + "gdprUrl": "https://www.pangleglobal.com/privacy", + "wildcard": "0" + } + ], + "u": [ + { + "platform": "Totvs", + "category": "Marketing", + "name": "u", + "domain": "t.tailtarget.com", + "description": "This cookie is Used for audience segmentation for advertising", + "retention": "1 year", + "dataController": "Totvs", + "gdprUrl": "https://www.totvs.com/protecao-e-privacidade-de-dados/", + "wildcard": "0" + } + ], + "_ssc": [ + { + "platform": "Totvs", + "category": "Marketing", + "name": "_ssc", + "domain": "t.tailtarget.com", + "description": "This is a cookie used for generating access and internet traffic statistics.", + "retention": "1 day", + "dataController": "Totvs", + "gdprUrl": "https://www.totvs.com/protecao-e-privacidade-de-dados/", + "wildcard": "0" + } + ], + "NSC_*": [ + { + "platform": "Citrix", + "category": "Functional", + "name": "NSC_*", + "domain": "", + "description": "This cookie name is associated with the Netscaler load balancing service from Citrix. This is a pattern type cookie with the root being NSC_ and the rest of the name being a unique encrypted alpha numeric identifier for the virtual server it originated from. The cookie is used to ensure traffic and user data is routed to the correct locations where a site is hosted on multiple servers, so that the end user has a consistent experience.", + "retention": "12 hours", + "dataController": "Citrix", + "gdprUrl": "https://www.citrix.com/about/trust-center/privacy-compliance/", + "wildcard": "1" + } + ], + "bitoIsSecure": [ + { + "platform": "Beeswax", + "category": "Marketing", + "name": "bitoIsSecure", + "domain": "bidr.io", + "description": "This cookie is associated with bidr.io. It allows third party advertisers to target the visitor with relevant advertising. This pairing service is provided by third party advertisement hubs, which facilitate real-time bidding for advertisers.", + "retention": "1 year", + "dataController": "Beeswax", + "gdprUrl": "https://www.beeswax.com/privacy/", + "wildcard": "0" + } + ], + "bito": [ + { + "platform": "Beeswax", + "category": "Marketing", + "name": "bito", + "domain": "bidr.io", + "description": "This cookie is generally provided by bidr.io and is used for advertising purposes.", + "retention": "1 year", + "dataController": "Beeswax", + "gdprUrl": "https://www.beeswax.com/privacy/", + "wildcard": "0" + } + ], + "WMF-Last-Access": [ + { + "platform": "Wikimedia", + "category": "Analytics", + "name": "WMF-Last-Access", + "domain": ".wikimedia.org", + "description": "This cookie is used by the Wikimedia Foundation. It is used to determine the last time a user visited a page, and is used for various statistics.", + "retention": "session", + "dataController": "Wikipedia", + "gdprUrl": "https://foundation.wikimedia.org/wiki/Policy:Privacy_policy", + "wildcard": "0" + } + ], + "loginnotify_prevlogins": [ + { + "platform": "Wikimedia", + "category": "Functional", + "name": "loginnotify_prevlogins", + "domain": ".wikimedia.org", + "description": "This cookie verifies that you are logging in from a known device. This affects the threshold for how many unsuccessful login attempts trigger a notification to the user..", + "retention": "180 days", + "dataController": "Wikipedia", + "gdprUrl": "https://foundation.wikimedia.org/wiki/Policy:Privacy_policy", + "wildcard": "0" + } + ], + "stopMobileRedirect": [ + { + "platform": "Wikimedia", + "category": "Functional", + "name": "stopMobileRedirect", + "domain": ".wikimedia.org", + "description": "This cookie tells us not to redirect to the mobile site if you do not like that..", + "retention": "30 days", + "dataController": "Wikipedia", + "gdprUrl": "https://foundation.wikimedia.org/wiki/Policy:Privacy_policy", + "wildcard": "0" + } + ], + "centralnotice_bucket": [ + { + "platform": "Wikimedia", + "category": "Analytics", + "name": "centralnotice_bucket", + "domain": ".wikimedia.org", + "description": "This cookie helps us understand the effectiveness of notices provided to users through the CentralNotice extension..", + "retention": "session", + "dataController": "Wikipedia", + "gdprUrl": "https://foundation.wikimedia.org/wiki/Policy:Privacy_policy", + "wildcard": "0" + } + ], + "GeoIP": [ + { + "platform": "Wikimedia", + "category": "Analytics", + "name": "GeoIP", + "domain": ".wikimedia.org", + "description": "This cookie is used to try and understand the user's geographical location (country) based on their IP address.", + "retention": "session", + "dataController": "Wikipedia", + "gdprUrl": "https://foundation.wikimedia.org/wiki/Policy:Privacy_policy", + "wildcard": "0" + } + ], + "NetWorkProbeLimit": [ + { + "platform": "Wikimedia", + "category": "Analytics", + "name": "NetWorkProbeLimit", + "domain": ".wikimedia.org", + "description": "This cookie is used to set NetworkProbeLimit cookie to override the default network probe limit value.", + "retention": "1 hour", + "dataController": "Wikipedia", + "gdprUrl": "https://foundation.wikimedia.org/wiki/Policy:Privacy_policy", + "wildcard": "0" + } + ], + "auid": [ + { + "platform": "Acuity", + "category": "Marketing", + "name": "auid", + "domain": ".acuityplatform.com", + "description": "This cookie is used to identify the visitor and cookie-tracking solutions and marketing and advertising services..", + "retention": "1 year", + "dataController": "Acuity", + "gdprUrl": "", + "wildcard": "0" + } + ], + "aum": [ + { + "platform": "Acuity", + "category": "Marketing", + "name": "aum", + "domain": ".acuityplatform.com", + "description": "This cookie is used to identify the visitor and the company provides a range of cookie-tracking solutions and marketing and advertising services.", + "retention": "1 year", + "dataController": "Acuity", + "gdprUrl": "", + "wildcard": "0" + } + ], + "ablyft_exps": [ + { + "platform": "ABlyft", + "category": "Analytics", + "name": "ablyft_exps", + "domain": "", + "description": "Is set and updated when a visitor is bucketed into an experiment/variation.", + "retention": "1 year", + "dataController": "ABlyft", + "gdprUrl": "https://ablyft.com/en/privacy-notice", + "wildcard": "0" + } + ], + "ablyft_queue": [ + { + "platform": "ABlyft", + "category": "Analytics", + "name": "ablyft_queue", + "domain": "", + "description": "Is set when a visitor triggers an event/goal. After sending the event to ABlyft it is cleared.", + "retention": "1 year", + "dataController": "ABlyft", + "gdprUrl": "https://ablyft.com/en/privacy-notice", + "wildcard": "0" + } + ], + "ablyft_uvs": [ + { + "platform": "ABlyft", + "category": "Analytics", + "name": "ablyft_uvs", + "domain": "", + "description": "Is set on the first pageview and update with every further pageview of a visitor.", + "retention": "1 year", + "dataController": "ABlyft", + "gdprUrl": "https://ablyft.com/en/privacy-notice", + "wildcard": "0" + } + ], + "ablyft_tracking_consent": [ + { + "platform": "ABlyft", + "category": "Analytics", + "name": "ablyft_tracking_consent", + "domain": "", + "description": "Is set when enableTrackingConsent or disableTrackingConsent is triggered via API.", + "retention": "1 year", + "dataController": "ABlyft", + "gdprUrl": "https://ablyft.com/en/privacy-notice", + "wildcard": "0" + } + ], + "_d2id": [ + { + "platform": "MercadoLibre", + "category": "Marketing", + "name": "_d2id", + "domain": ".mercadolibre.com", + "description": "This cookie is required for shopping cart functionality on the website.", + "retention": "1 year", + "dataController": "MercadoLibre", + "gdprUrl": "https://www.mercadolibre.com.ar/privacidad", + "wildcard": "0" + } + ], + "edsid": [ + { + "platform": "MercadoLibre", + "category": "Marketing", + "name": "edsid", + "domain": ".mercadolibre.com", + "description": "This cookie is used to identify users to implement fraud prevention", + "retention": "1 year", + "dataController": "MercadoLibre", + "gdprUrl": "https://www.mercadolibre.com.ar/privacidad", + "wildcard": "" + } + ], + "ftid": [ + { + "platform": "MercadoLibre", + "category": "Marketing", + "name": "ftid", + "domain": ".mercadolibre.com", + "description": "This cookie is used to identify users to implement fraud prevention", + "retention": "1 year", + "dataController": "MercadoLibre", + "gdprUrl": "https://www.mercadolibre.com.ar/privacidad", + "wildcard": "0" + } + ], + "aniC": [ + { + "platform": "Aniview", + "category": "Marketing", + "name": "aniC", + "domain": ".aniview.com", + "description": "This cookie is used in context with video-advertisement. The cookie limits the number of times a user is shown the same advertisement. The cookie is also used to ensure relevance of the video-advertisement to the specific user.", + "retention": "20 Days", + "dataController": "Aniview", + "gdprUrl": "https://www.aniview.com/privacy-policy/", + "wildcard": "0" + } + ], + "version": [ + { + "platform": "Anivew", + "category": "Marketing", + "name": "version", + "domain": "track1.aniview.com", + "description": "This cookie is used by the website's operator in context with multi-variate testing. This is a tool used to combine or change content on the website. This allows the website to find the best variation/edition of the site.", + "retention": "Session", + "dataController": "Aniview", + "gdprUrl": "https://www.aniview.com/privacy-policy/", + "wildcard": "0" + } ] } diff --git a/bin/chrome-3pcd-ps.bat b/bin/chrome-3pcd-ps.bat index d04ce3ab8..ebf6c6c99 100644 --- a/bin/chrome-3pcd-ps.bat +++ b/bin/chrome-3pcd-ps.bat @@ -1,7 +1,7 @@ :: Chrome 3pcd with PS Extension :: Download PS Extension -set "ps_analysis_tool_version=v0.6.0" +set "ps_analysis_tool_version=v0.7.0" cd /d %TEMP% if not exist %TEMP%\ps-analysis-tool-%ps_analysis_tool_version% ( mkdir %TEMP%\ps-analysis-tool-%ps_analysis_tool_version% diff --git a/bin/chrome-default-ps.bat b/bin/chrome-default-ps.bat index 2d98b65b9..b7f03949c 100644 --- a/bin/chrome-default-ps.bat +++ b/bin/chrome-default-ps.bat @@ -1,7 +1,7 @@ :: Default Chrome with PS Extension :: Download PS Extension -set "ps_analysis_tool_version=v0.6.0" +set "ps_analysis_tool_version=v0.7.0" cd /d %TEMP% if not exist %TEMP%\ps-analysis-tool-%ps_analysis_tool_version% ( mkdir %TEMP%\ps-analysis-tool-%ps_analysis_tool_version% diff --git a/bin/chrome_launcher.sh b/bin/chrome_launcher.sh index e5cd3610c..356dd3d5a 100644 --- a/bin/chrome_launcher.sh +++ b/bin/chrome_launcher.sh @@ -2,7 +2,7 @@ # Download Extension extension_setup() { - ps_analysis_tool_version=v0.6.0 + ps_analysis_tool_version=v0.7.0 extension_dir="/var/tmp" cd $extension_dir if [ ! -d $extension_dir/ps-analysis-tool-$ps_analysis_tool_version ]; then diff --git a/data/related_website_sets.json b/data/related_website_sets.json index 3e34383a3..fa8da07de 100644 --- a/data/related_website_sets.json +++ b/data/related_website_sets.json @@ -608,6 +608,19 @@ "https://startupislandtaiwan.org": "Domain alias" } }, + { + "contact": "k.vermote@eurofleet-consult.com", + "primary": "https://carcostadvisor.com", + "associatedSites": [], + "serviceSites": [], + "rationaleBySite": {}, + "ccTLDs": { + "https://carcostadvisor.com": [ + "https://carcostadvisor.be", + "https://carcostadvisor.fr" + ] + } + }, { "contact": "addigital@caracoltv.com.co", "primary": "https://caracoltv.com", @@ -645,6 +658,36 @@ "rationaleBySite": { "https://wordle.at": "We are migrating our domain and will soon redirect all traffic from here to the primary, both of which we own. The two sites are almost identical. For convenience we want to transfer session cookies so users stay logged in." } + }, + { + "primary": "https://blackrock.com", + "contact": "extcontactcwp@blackrock.com", + "associatedSites": [ + "https://blackrockadvisorelite.it", + "https://cachematrix.com", + "https://efront.com", + "https://etfacademy.it", + "https://ishares.com" + ], + "rationaleBySite": { + "https://blackrockadvisorelite.it": "A site for Italian investment professionals. The branding is clearly visible on the site.", + "https://cachematrix.com": "Cachematrix is a cash management firm acquired by BlackRock. The relationship is described on the About Us page.", + "https://efront.com": "eFront is BlackRock's alternative solutions platform. The BlackRock branding is clearly visible on the site.", + "https://etfacademy.it": "An Italian language education site about ETFs. The iShares by BlackRock branding is clearly visible on the site.", + "https://ishares.com": "iShares is BlackRock's ETF brand. The branding is clearly visible on the site." + } + }, + { + "contact": "ewelina.salamonik@wbd.com", + "primary": "https://tvn.pl", + "associatedSites": [ + "https://tvn24.pl", + "https://zdrowietvn.pl" + ], + "rationaleBySite": { + "https://zdrowietvn.pl": "Educational service that is owned by TVN S.A.. Information about the connection with TVN is included in the footer of this website", + "https://tvn24.pl": "News service that is owned by TVN S.A.. Information about the connection with TVN is included in the footer of this website" + } } ] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index db3abf4ee..0cc70cb30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ps-analysis-tool", - "version": "0.6.0", + "version": "0.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ps-analysis-tool", - "version": "0.6.0", + "version": "0.7.0", "license": "Apache-2.0", "workspaces": [ "packages/*" @@ -94,9 +94,8 @@ }, "node_modules/@adobe/css-tools": { "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", - "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", @@ -2290,6 +2289,50 @@ "version": "0.1.6", "license": "MIT" }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.2.tgz", + "integrity": "sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA==", + "dependencies": { + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", + "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.7.6", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.6.tgz", + "integrity": "sha512-etVau26po9+eewJKYoiBKP6743I1br0/Ie00Pb/S/PtmYfmjTcOn2YCh2yNkSZI12h6Rg+BOgQYborXk46BvkA==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.18.2", + "@formatjs/icu-skeleton-parser": "1.8.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.0.tgz", + "integrity": "sha512-QWLAYvM0n8hv7Nq5BEs4LKIjevpVpbGLAJgOaYzg9wABEoX1j0JO1q2/jVkO6CVlq0dbsxZCngS5aXbysYueqA==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.18.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "dev": true, @@ -3278,6 +3321,10 @@ "resolved": "packages/extension", "link": true }, + "node_modules/@ps-analysis-tool/i18n": { + "resolved": "packages/i18n", + "link": true + }, "node_modules/@ps-analysis-tool/library-detection": { "resolved": "packages/library-detection", "link": true @@ -6879,9 +6926,8 @@ }, "node_modules/@types/xml2js": { "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", - "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -8429,9 +8475,8 @@ }, "node_modules/body-parser": { "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dev": true, + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -8453,24 +8498,21 @@ }, "node_modules/body-parser/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.0.0" } }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/body-parser/node_modules/qs": { "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.4" }, @@ -8631,9 +8673,8 @@ }, "node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -9469,9 +9510,8 @@ }, "node_modules/content-type": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9483,9 +9523,8 @@ }, "node_modules/cookie": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -10979,9 +11018,8 @@ }, "node_modules/es5-ext": { "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "hasInstallScript": true, + "license": "ISC", "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", @@ -11705,8 +11743,7 @@ }, "node_modules/esniff": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", @@ -11719,8 +11756,7 @@ }, "node_modules/esniff/node_modules/type": { "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + "license": "ISC" }, "node_modules/espree": { "version": "9.6.1", @@ -11902,9 +11938,8 @@ }, "node_modules/express": { "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dev": true, + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -12385,8 +12420,6 @@ }, "node_modules/follow-redirects": { "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -12394,6 +12427,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -13245,6 +13279,20 @@ "dev": true, "license": "MIT" }, + "node_modules/html-inline-script-webpack-plugin": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/html-inline-script-webpack-plugin/-/html-inline-script-webpack-plugin-3.2.1.tgz", + "integrity": "sha512-PEj9Ve31BE0dva6eTD6wHMOztgIdPxF6gx3wad7ohBkCn7MXpuUvPC9t5ThMJ2NrVi1jWGBYU76DfoS+8dabRw==", + "dev": true, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + }, + "peerDependencies": { + "html-webpack-plugin": "^5.0.0", + "webpack": "^5.0.0" + } + }, "node_modules/html-minifier-terser": { "version": "6.1.0", "dev": true, @@ -13453,9 +13501,8 @@ }, "node_modules/iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -13672,6 +13719,17 @@ "node": ">=10.13.0" } }, + "node_modules/intl-messageformat": { + "version": "10.5.11", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.11.tgz", + "integrity": "sha512-eYq5fkFBVxc7GIFDzpFQkDOZgNayNTQn4Oufe8jw6YY6OHVw70/4pA3FyCsQ0Gb2DnvEJEMmN2tOaXUGByM+kg==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.18.2", + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/icu-messageformat-parser": "2.7.6", + "tslib": "^2.4.0" + } + }, "node_modules/invariant": { "version": "2.2.4", "dev": true, @@ -13682,8 +13740,7 @@ }, "node_modules/ip": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", - "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" + "license": "MIT" }, "node_modules/ipaddr.js": { "version": "1.9.1", @@ -17161,9 +17218,8 @@ }, "node_modules/media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -18260,8 +18316,7 @@ }, "node_modules/pac-resolver": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" @@ -19237,9 +19292,8 @@ }, "node_modules/raw-body": { "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -19260,8 +19314,7 @@ }, "node_modules/react": { "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" }, @@ -21428,9 +21481,10 @@ } }, "node_modules/tar": { - "version": "6.2.0", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, - "license": "ISC", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -22125,9 +22179,8 @@ }, "node_modules/type-is": { "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -22476,8 +22529,7 @@ }, "node_modules/use-context-selector": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/use-context-selector/-/use-context-selector-1.4.1.tgz", - "integrity": "sha512-Io2ArvcRO+6MWIhkdfMFt+WKQX+Vb++W8DS2l03z/Vw/rz3BclKpM0ynr4LYGyU85Eke+Yx5oIhTY++QR0ZDoA==", + "license": "MIT", "peerDependencies": { "react": ">=16.8.0", "react-dom": "*", @@ -23432,9 +23484,8 @@ }, "node_modules/webpack-dev-middleware": { "version": "6.1.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.2.tgz", - "integrity": "sha512-Wu+EHmX326YPYUpQLKmKbTyZZJIB8/n6R09pTmB03kJmnMsVPTo9COzHZFr01txwaCAuZvfBJE4ZCHRcKs5JaQ==", "dev": true, + "license": "MIT", "dependencies": { "colorette": "^2.0.10", "memfs": "^3.4.12", @@ -23657,9 +23708,8 @@ }, "node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": { "version": "5.3.4", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", - "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dev": true, + "license": "MIT", "dependencies": { "colorette": "^2.0.10", "memfs": "^3.4.3", @@ -24244,7 +24294,7 @@ }, "packages/cli": { "name": "@ps-analysis-tool/cli", - "version": "0.6.0", + "version": "0.7.0", "license": "Apache-2.0", "dependencies": { "@ps-analysis-tool/common": "*", @@ -24274,7 +24324,7 @@ }, "packages/cli-dashboard": { "name": "@ps-analysis-tool/cli-dashboard", - "version": "0.6.0", + "version": "0.7.0", "license": "Apache-2.0", "dependencies": { "@ps-analysis-tool/common": "*", @@ -25001,8 +25051,7 @@ }, "packages/cli/node_modules/xml2js": { "version": "0.6.2", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", - "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -25013,24 +25062,24 @@ }, "packages/common": { "name": "@ps-analysis-tool/common", - "version": "0.6.0", + "version": "0.7.0", "license": "Apache-2.0", "dependencies": { "tldts": "^6.0.14" }, "devDependencies": { - "devtools-protocol": "^0.0.1236148" + "devtools-protocol": "^0.0.1282316" } }, "packages/common/node_modules/devtools-protocol": { - "version": "0.0.1236148", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1236148.tgz", - "integrity": "sha512-fCOlpTqzzeCpbHaYUJAraMIP1a8D4FNzHDnUaIfZCeK9XMUONKjfLISkLXX56wDgQ93omAiTAFoSR3maAGz5Dg==", + "version": "0.0.1282316", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1282316.tgz", + "integrity": "sha512-i7eIqWdVxeXBY/M+v83yRkOV1sTHnr3XYiC0YNBivLIE6hBfE2H0c2o8VC5ynT44yjy+Ei0kLrBQFK/RUKaAHQ==", "dev": true }, "packages/design-system": { "name": "@ps-analysis-tool/design-system", - "version": "0.6.0", + "version": "0.7.0", "license": "Apache-2.0", "dependencies": { "@ps-analysis-tool/common": "*", @@ -25041,7 +25090,7 @@ }, "packages/eslint-import-resolver": { "name": "@ps-analysis-tool/eslint-import-resolver", - "version": "0.6.0", + "version": "0.7.0", "license": "Apache-2.0", "dependencies": { "eslint-import-resolver-node": "^0.3.7" @@ -25049,7 +25098,7 @@ }, "packages/extension": { "name": "@ps-analysis-tool/extension", - "version": "0.6.0", + "version": "0.7.0", "license": "Apache-2.0", "dependencies": { "@floating-ui/core": "^1.5.0", @@ -25076,18 +25125,27 @@ }, "devDependencies": { "@types/react-copy-to-clipboard": "^5.0.4", - "devtools-protocol": "^0.0.1236148" + "devtools-protocol": "^0.0.1282316", + "html-inline-script-webpack-plugin": "^3.2.1" } }, "packages/extension/node_modules/devtools-protocol": { - "version": "0.0.1236148", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1236148.tgz", - "integrity": "sha512-fCOlpTqzzeCpbHaYUJAraMIP1a8D4FNzHDnUaIfZCeK9XMUONKjfLISkLXX56wDgQ93omAiTAFoSR3maAGz5Dg==", + "version": "0.0.1282316", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1282316.tgz", + "integrity": "sha512-i7eIqWdVxeXBY/M+v83yRkOV1sTHnr3XYiC0YNBivLIE6hBfE2H0c2o8VC5ynT44yjy+Ei0kLrBQFK/RUKaAHQ==", "dev": true }, + "packages/i18n": { + "name": "@ps-analysis-tool/i18n", + "version": "0.7.0", + "license": "Apache-2.0", + "dependencies": { + "intl-messageformat": "^10.5.11" + } + }, "packages/library-detection": { "name": "@ps-analysis-tool/library-detection", - "version": "0.6.0", + "version": "0.7.0", "license": "Apache-2.0", "dependencies": { "@ps-analysis-tool/common": "*", @@ -25100,13 +25158,11 @@ }, "packages/library-detection/node_modules/classnames": { "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + "license": "MIT" }, "packages/library-detection/node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -25122,8 +25178,6 @@ }, "@adobe/css-tools": { "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", - "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", "dev": true }, "@alloc/quick-lru": { @@ -26433,6 +26487,50 @@ "@floating-ui/utils": { "version": "0.1.6" }, + "@formatjs/ecma402-abstract": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.2.tgz", + "integrity": "sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA==", + "requires": { + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "@formatjs/fast-memoize": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", + "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", + "requires": { + "tslib": "^2.4.0" + } + }, + "@formatjs/icu-messageformat-parser": { + "version": "2.7.6", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.6.tgz", + "integrity": "sha512-etVau26po9+eewJKYoiBKP6743I1br0/Ie00Pb/S/PtmYfmjTcOn2YCh2yNkSZI12h6Rg+BOgQYborXk46BvkA==", + "requires": { + "@formatjs/ecma402-abstract": "1.18.2", + "@formatjs/icu-skeleton-parser": "1.8.0", + "tslib": "^2.4.0" + } + }, + "@formatjs/icu-skeleton-parser": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.0.tgz", + "integrity": "sha512-QWLAYvM0n8hv7Nq5BEs4LKIjevpVpbGLAJgOaYzg9wABEoX1j0JO1q2/jVkO6CVlq0dbsxZCngS5aXbysYueqA==", + "requires": { + "@formatjs/ecma402-abstract": "1.18.2", + "tslib": "^2.4.0" + } + }, + "@formatjs/intl-localematcher": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", + "requires": { + "tslib": "^2.4.0" + } + }, "@humanwhocodes/config-array": { "version": "0.11.13", "dev": true, @@ -27133,8 +27231,6 @@ }, "xml2js": { "version": "0.6.2", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", - "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", "requires": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -27524,14 +27620,14 @@ "@ps-analysis-tool/common": { "version": "file:packages/common", "requires": { - "devtools-protocol": "^0.0.1236148", + "devtools-protocol": "^0.0.1282316", "tldts": "^6.0.14" }, "dependencies": { "devtools-protocol": { - "version": "0.0.1236148", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1236148.tgz", - "integrity": "sha512-fCOlpTqzzeCpbHaYUJAraMIP1a8D4FNzHDnUaIfZCeK9XMUONKjfLISkLXX56wDgQ93omAiTAFoSR3maAGz5Dg==", + "version": "0.0.1282316", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1282316.tgz", + "integrity": "sha512-i7eIqWdVxeXBY/M+v83yRkOV1sTHnr3XYiC0YNBivLIE6hBfE2H0c2o8VC5ynT44yjy+Ei0kLrBQFK/RUKaAHQ==", "dev": true } } @@ -27560,9 +27656,10 @@ "@ps-analysis-tool/library-detection": "*", "@types/react-copy-to-clipboard": "^5.0.4", "classnames": "^2.3.2", - "devtools-protocol": "^0.0.1236148", + "devtools-protocol": "^0.0.1282316", "fast-xml-parser": "^4.3.2", "file-saver": "^2.0.5", + "html-inline-script-webpack-plugin": "^3.2.1", "p-queue": "^7.3.4", "re-resizable": "^6.9.9", "react": "^18.2.0", @@ -27579,13 +27676,19 @@ }, "dependencies": { "devtools-protocol": { - "version": "0.0.1236148", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1236148.tgz", - "integrity": "sha512-fCOlpTqzzeCpbHaYUJAraMIP1a8D4FNzHDnUaIfZCeK9XMUONKjfLISkLXX56wDgQ93omAiTAFoSR3maAGz5Dg==", + "version": "0.0.1282316", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1282316.tgz", + "integrity": "sha512-i7eIqWdVxeXBY/M+v83yRkOV1sTHnr3XYiC0YNBivLIE6hBfE2H0c2o8VC5ynT44yjy+Ei0kLrBQFK/RUKaAHQ==", "dev": true } } }, + "@ps-analysis-tool/i18n": { + "version": "file:packages/i18n", + "requires": { + "intl-messageformat": "^10.5.11" + } + }, "@ps-analysis-tool/library-detection": { "version": "file:packages/library-detection", "requires": { @@ -27598,14 +27701,10 @@ }, "dependencies": { "classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + "version": "2.5.1" }, "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + "version": "4.0.0" } } }, @@ -29862,8 +29961,6 @@ }, "@types/xml2js": { "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", - "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==", "dev": true, "requires": { "@types/node": "*" @@ -30838,8 +30935,6 @@ }, "body-parser": { "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dev": true, "requires": { "bytes": "3.1.2", @@ -30858,8 +30953,6 @@ "dependencies": { "debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" @@ -30867,14 +30960,10 @@ }, "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, "qs": { "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "dev": true, "requires": { "side-channel": "^1.0.4" @@ -30972,8 +31061,6 @@ }, "bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true }, "c8": { @@ -31503,8 +31590,6 @@ }, "content-type": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true }, "convert-source-map": { @@ -31513,8 +31598,6 @@ }, "cookie": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "dev": true }, "cookie-signature": { @@ -32478,8 +32561,6 @@ }, "es5-ext": { "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "requires": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", @@ -32951,8 +33032,6 @@ }, "esniff": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", "requires": { "d": "^1.0.1", "es5-ext": "^0.10.62", @@ -32961,9 +33040,7 @@ }, "dependencies": { "type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + "version": "2.7.2" } } }, @@ -33083,8 +33160,6 @@ }, "express": { "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dev": true, "requires": { "accepts": "~1.3.8", @@ -33435,8 +33510,6 @@ }, "follow-redirects": { "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true }, "for-each": { @@ -33963,6 +34036,13 @@ "version": "2.0.2", "dev": true }, + "html-inline-script-webpack-plugin": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/html-inline-script-webpack-plugin/-/html-inline-script-webpack-plugin-3.2.1.tgz", + "integrity": "sha512-PEj9Ve31BE0dva6eTD6wHMOztgIdPxF6gx3wad7ohBkCn7MXpuUvPC9t5ThMJ2NrVi1jWGBYU76DfoS+8dabRw==", + "dev": true, + "requires": {} + }, "html-minifier-terser": { "version": "6.1.0", "dev": true, @@ -34088,8 +34168,6 @@ }, "iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -34209,6 +34287,17 @@ "version": "3.1.1", "dev": true }, + "intl-messageformat": { + "version": "10.5.11", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.11.tgz", + "integrity": "sha512-eYq5fkFBVxc7GIFDzpFQkDOZgNayNTQn4Oufe8jw6YY6OHVw70/4pA3FyCsQ0Gb2DnvEJEMmN2tOaXUGByM+kg==", + "requires": { + "@formatjs/ecma402-abstract": "1.18.2", + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/icu-messageformat-parser": "2.7.6", + "tslib": "^2.4.0" + } + }, "invariant": { "version": "2.2.4", "dev": true, @@ -34217,9 +34306,7 @@ } }, "ip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", - "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" + "version": "2.0.1" }, "ipaddr.js": { "version": "1.9.1", @@ -36367,8 +36454,6 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true }, "memfs": { @@ -37066,8 +37151,6 @@ }, "pac-resolver": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", "requires": { "degenerator": "^5.0.0", "netmask": "^2.0.2" @@ -37640,8 +37723,6 @@ }, "raw-body": { "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "requires": { "bytes": "3.1.2", @@ -37656,8 +37737,6 @@ }, "react": { "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "requires": { "loose-envify": "^1.1.0" } @@ -39058,7 +39137,9 @@ "dev": true }, "tar": { - "version": "6.2.0", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, "requires": { "chownr": "^2.0.0", @@ -39517,8 +39598,6 @@ }, "type-is": { "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, "requires": { "media-typer": "0.3.0", @@ -39729,8 +39808,6 @@ }, "use-context-selector": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/use-context-selector/-/use-context-selector-1.4.1.tgz", - "integrity": "sha512-Io2ArvcRO+6MWIhkdfMFt+WKQX+Vb++W8DS2l03z/Vw/rz3BclKpM0ynr4LYGyU85Eke+Yx5oIhTY++QR0ZDoA==", "requires": {} }, "use-debounce": { @@ -40349,8 +40426,6 @@ }, "webpack-dev-middleware": { "version": "6.1.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.2.tgz", - "integrity": "sha512-Wu+EHmX326YPYUpQLKmKbTyZZJIB8/n6R09pTmB03kJmnMsVPTo9COzHZFr01txwaCAuZvfBJE4ZCHRcKs5JaQ==", "dev": true, "requires": { "colorette": "^2.0.10", @@ -40485,8 +40560,6 @@ }, "webpack-dev-middleware": { "version": "5.3.4", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", - "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dev": true, "requires": { "colorette": "^2.0.10", diff --git a/package.json b/package.json index a1719976f..d02400cde 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,18 @@ { "name": "ps-analysis-tool", - "version": "0.6.0", + "version": "0.7.0", "description": "Cookie Analysis Tool and CLI for analysis and understanding of cookie usage on web pages.", "scripts": { "cli:prebuild": "node ./scripts/delete-build-artifacts.cjs", "cli:dev": "tsc-watch --build", "cli:build": "npm run cli:prebuild && tsc --build", - "cli-dashboard:start": "npm run dev --workspace=@ps-analysis-tool/cli-dashboard", "cli-dashboard:dev": "npm run dev --workspace=@ps-analysis-tool/cli-dashboard", "cli-dashboard:build": "npm run build --workspace=@ps-analysis-tool/cli-dashboard", "cli": "node dist/cli/index.js", "test": "jest --config=tests/jest.config.cjs", "test:coverage": "npm run test -- --collectCoverage && open coverage/lcov-report/index.html", "dev": "npm run dev --workspace=@ps-analysis-tool/extension", - "build": "npm run build --workspace=@ps-analysis-tool/extension", + "build": "rm -rf dist/extension && npm run build --workspace=@ps-analysis-tool/extension", "serve": "webpack serve", "lint": "npm-run-all --parallel lint:*", "lint:js": "eslint .", @@ -22,6 +21,7 @@ "prepare": "husky install", "cookie-db:update": "node scripts/update-cookie-db.cjs", "rws-json:update": "node scripts/update-rws-json.cjs", + "merge-i18n-messages": "node packages/i18n/scripts/merge-messages.cjs", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", "start": "npm install && npm run dev" diff --git a/packages/cli-dashboard/package.json b/packages/cli-dashboard/package.json index 28a415dfc..147798102 100644 --- a/packages/cli-dashboard/package.json +++ b/packages/cli-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@ps-analysis-tool/cli-dashboard", - "version": "0.6.0", + "version": "0.7.0", "description": "Dashboard for visualizing cli analysis output", "repository": { "type": "git", diff --git a/packages/cli-dashboard/src/app.css b/packages/cli-dashboard/src/app.css index e03eb3376..28f8b7bc9 100644 --- a/packages/cli-dashboard/src/app.css +++ b/packages/cli-dashboard/src/app.css @@ -7,3 +7,12 @@ display: none; } } +@layer base { + body { + @apply font-sans; + } + :root { + --color-message-box-dark: 33deg 14% 18%; + --color-message-box-light: 30deg 100% 75%; + } +} diff --git a/packages/cli-dashboard/src/components/affectedCookies/index.tsx b/packages/cli-dashboard/src/components/cookiesWithIssues/index.tsx similarity index 79% rename from packages/cli-dashboard/src/components/affectedCookies/index.tsx rename to packages/cli-dashboard/src/components/cookiesWithIssues/index.tsx index 39ae7768b..d987c7768 100644 --- a/packages/cli-dashboard/src/components/affectedCookies/index.tsx +++ b/packages/cli-dashboard/src/components/cookiesWithIssues/index.tsx @@ -25,20 +25,28 @@ import { type CookieTableData } from '@ps-analysis-tool/common'; /** * Internal dependencies. */ -import useCookieListing from '../../hooks/useCookieListing.tsx'; +import useCookieListing from '../../hooks/useCookieListing'; -interface AffectedCookiesProps { +interface CookiesWithIssuesProps { cookies: CookieTableData[]; selectedSite: string | null; } -const AffectedCookies = ({ cookies, selectedSite }: AffectedCookiesProps) => { +const CookiesWithIssues = ({ + cookies, + selectedSite, +}: CookiesWithIssuesProps) => { const [selectedFrameCookie, setSelectedFrameCookie] = useState<{ [frame: string]: CookieTableData | null; } | null>(null); const { tableColumns, filters, searchKeys, tablePersistentSettingsKey } = - useCookieListing(cookies, 'frame', 'affectedCookiesListing', selectedSite); + useCookieListing( + cookies, + 'frame', + 'cookiesWithIssuesListing', + selectedSite + ); return (
@@ -58,10 +66,8 @@ const AffectedCookies = ({ cookies, selectedSite }: AffectedCookiesProps) => { className="h-full flex" > ({ ...cookie, isBlocked: undefined }))} // Hot Fix: To unhighlight cookies in the Affected Cookie table. - useIsBlockedToHighlight={true} // Hot Fix: To use isBlocked to highlight cookies in the Affected Cookie table. + data={cookies.map((cookie) => ({ ...cookie, isBlocked: undefined }))} // Hot Fix: To unhighlight cookies in the Cookies with issues table. tableColumns={tableColumns} - showTopBar={true} tableFilters={filters} tableSearchKeys={searchKeys} tablePersistentSettingsKey={tablePersistentSettingsKey} @@ -73,10 +79,10 @@ const AffectedCookies = ({ cookies, selectedSite }: AffectedCookiesProps) => {
); }; -export default AffectedCookies; +export default CookiesWithIssues; diff --git a/packages/cli-dashboard/src/components/siteMapReport/index.tsx b/packages/cli-dashboard/src/components/siteMapReport/index.tsx index c9521a1ff..fc0c3777b 100644 --- a/packages/cli-dashboard/src/components/siteMapReport/index.tsx +++ b/packages/cli-dashboard/src/components/siteMapReport/index.tsx @@ -17,19 +17,13 @@ /** * External dependencies. */ -import React, { useEffect, useMemo, useState } from 'react'; -import { Resizable } from 're-resizable'; +import React, { useState } from 'react'; import { - File, - FileWhite, - Sidebar, - useSidebar, + SidebarProvider, type SidebarItems, } from '@ps-analysis-tool/design-system'; import { - type TabFrames, type TechnologyData, - UNKNOWN_FRAME_KEY, type CookieFrameStorageType, type CompleteJson, } from '@ps-analysis-tool/common'; @@ -37,12 +31,8 @@ import { /** * Internal dependencies. */ -import SiteReport from '../siteReport'; -import SiteMapAffectedCookies from './sitemapAffectedCookies'; -import CookiesLandingContainer from '../siteReport/tabs/cookies/cookiesLandingContainer'; -import reshapeCookies from '../utils/reshapeCookies'; import sidebarData from './sidebarData'; -import { generateSiteMapReportandDownload } from '../utils/reportDownloader'; +import Layout from './layout'; interface SiteMapReportProps { landingPageCookies: CookieFrameStorageType; @@ -57,168 +47,19 @@ const SiteMapReport = ({ landingPageCookies, completeJson, }: SiteMapReportProps) => { - const [sites, setSites] = useState([]); const [data, setData] = useState(sidebarData); - useEffect(() => { - const _sites = new Set(); - Object.values(cookies).forEach((cookieData) => { - Object.values(cookieData).forEach((cookie) => { - _sites.add(cookie.pageUrl || ''); - }); - }); - - setSites(Array.from(_sites)); - }, [cookies]); - - const frames = useMemo(() => { - return Object.keys(cookies).reduce((acc, frame) => { - if (frame?.includes('http') || frame === UNKNOWN_FRAME_KEY) { - acc[frame] = {} as TabFrames[string]; - } - return acc; - }, {} as TabFrames); - }, [cookies]); - - const reshapedCookies = useMemo( - () => reshapeCookies(landingPageCookies), - [landingPageCookies] - ); - - const affectedCookies = useMemo( - () => - Object.fromEntries( - Object.entries(reshapedCookies).filter(([, cookie]) => cookie.isBlocked) - ), - [reshapedCookies] - ); - - const { - activePanel, - selectedItemKey, - sidebarItems, - isSidebarFocused, - setIsSidebarFocused, - updateSelectedItemKey, - onKeyNavigation, - toggleDropdown, - isKeyAncestor, - isKeySelected, - } = useSidebar({ data }); - - const siteFilteredCookies = useMemo(() => { - return Object.entries(cookies).reduce( - (acc: CookieFrameStorageType, [frame, _cookies]) => { - acc[frame] = Object.fromEntries( - Object.entries(_cookies).filter(([, cookie]) => - isKeySelected(cookie.pageUrl || '') - ) - ); - - return acc; - }, - {} - ); - }, [cookies, isKeySelected]); - - const siteFilteredTechnologies = useMemo(() => { - return technologies.filter((technology) => - isKeySelected(technology.pageUrl || '') - ); - }, [isKeySelected, technologies]); - - useEffect(() => { - setData((prev) => { - const _data = { ...prev }; - - _data['sitemap-landing-page'].panel = ( - { - if (!Array.isArray(completeJson)) { - return; - } - - generateSiteMapReportandDownload(completeJson); - }} - /> - ); - - _data['sitemap-landing-page'].children = sites.reduce( - (acc: SidebarItems, site: string) => { - acc[site] = { - title: site, - panel: ( - - ), - children: {}, - icon: , - selectedIcon: , - }; - - return acc; - }, - {} - ); - - _data['sitemap-affected-cookies'].panel = ( - cookie.isBlocked - )} - /> - ); - - return _data; - }); - }, [ - affectedCookies, - completeJson, - frames, - isKeySelected, - reshapedCookies, - siteFilteredCookies, - siteFilteredTechnologies, - sites, - ]); - - useEffect(() => { - if (selectedItemKey === null && Object.keys(data).length > 0) { - updateSelectedItemKey('sitemap-landing-page'); - } - }, [data, isKeySelected, selectedItemKey, updateSelectedItemKey]); - return ( -
- - - -
{activePanel}
-
+ + + ); }; diff --git a/packages/cli-dashboard/src/components/siteMapReport/layout.tsx b/packages/cli-dashboard/src/components/siteMapReport/layout.tsx new file mode 100644 index 000000000..ef381cc60 --- /dev/null +++ b/packages/cli-dashboard/src/components/siteMapReport/layout.tsx @@ -0,0 +1,218 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies. + */ +import React, { useEffect, useMemo, useState } from 'react'; +import { Resizable } from 're-resizable'; +import { + File, + FileWhite, + Sidebar, + useSidebar, + type SidebarItems, + SIDEBAR_ITEMS_KEYS, +} from '@ps-analysis-tool/design-system'; +import { + type TabFrames, + type TechnologyData, + type CookieFrameStorageType, + type CompleteJson, +} from '@ps-analysis-tool/common'; + +/** + * Internal dependencies. + */ +import SiteReport from '../siteReport'; +import SiteMapCookiesWithIssues from './sitemapCookiesWithIssues'; +import CookiesLandingContainer from '../siteReport/tabs/cookies/cookiesLandingContainer'; +import reshapeCookies from '../utils/reshapeCookies'; +import { generateSiteMapReportandDownload } from '../utils/reportDownloader'; + +interface LayoutProps { + landingPageCookies: CookieFrameStorageType; + cookies: CookieFrameStorageType; + technologies: TechnologyData[]; + completeJson: CompleteJson[] | null; + sidebarData: SidebarItems; + setSidebarData: React.Dispatch>; +} + +const Layout = ({ + cookies, + technologies, + landingPageCookies, + completeJson, + sidebarData, + setSidebarData, +}: LayoutProps) => { + const [sites, setSites] = useState([]); + + useEffect(() => { + const _sites = new Set(); + Object.values(cookies).forEach((cookieData) => { + Object.values(cookieData).forEach((cookie) => { + _sites.add(cookie.pageUrl || ''); + }); + }); + + setSites(Array.from(_sites)); + }, [cookies]); + + const reshapedCookies = useMemo( + () => reshapeCookies(landingPageCookies), + [landingPageCookies] + ); + + const cookiesWithIssues = useMemo( + () => + Object.fromEntries( + Object.entries(reshapedCookies).filter(([, cookie]) => cookie.isBlocked) + ), + [reshapedCookies] + ); + + const { activePanel, selectedItemKey, updateSelectedItemKey, isKeySelected } = + useSidebar(({ state, actions }) => ({ + activePanel: state.activePanel, + selectedItemKey: state.selectedItemKey, + updateSelectedItemKey: actions.updateSelectedItemKey, + isKeySelected: actions.isKeySelected, + })); + + const { Element: PanelElement, props } = activePanel.panel; + + const siteFilteredCookies = useMemo(() => { + return Object.entries(cookies).reduce( + (acc: CookieFrameStorageType, [frame, _cookies]) => { + acc[frame] = Object.fromEntries( + Object.entries(_cookies).filter(([, cookie]) => + isKeySelected(cookie.pageUrl || '') + ) + ); + + return acc; + }, + {} + ); + }, [cookies, isKeySelected]); + + const siteFilteredTechnologies = useMemo(() => { + return technologies.filter((technology) => + isKeySelected(technology.pageUrl || '') + ); + }, [isKeySelected, technologies]); + + useEffect(() => { + setSidebarData((prev) => { + const _data = { ...prev }; + + _data[SIDEBAR_ITEMS_KEYS.COOKIES].panel = { + Element: CookiesLandingContainer, + props: { + tabCookies: reshapedCookies, + tabFrames: sites.reduce((acc, site) => { + acc[site] = {} as TabFrames[string]; + + return acc; + }, {}), + cookiesWithIssues, + downloadReport: () => { + if (!Array.isArray(completeJson)) { + return; + } + + generateSiteMapReportandDownload(completeJson); + }, + }, + }; + + _data[SIDEBAR_ITEMS_KEYS.COOKIES].children = sites.reduce( + (acc: SidebarItems, site: string) => { + acc[site] = { + title: site, + panel: { + Element: SiteReport, + props: { + cookies: siteFilteredCookies, + technologies: siteFilteredTechnologies, + completeJson, + selectedSite: site, + }, + }, + children: {}, + icon: { + Element: File, + }, + selectedIcon: { + Element: FileWhite, + }, + }; + + return acc; + }, + {} + ); + + _data[SIDEBAR_ITEMS_KEYS.COOKIES_WITH_ISSUES].panel = { + Element: SiteMapCookiesWithIssues, + props: { + cookies: Object.values(reshapedCookies).filter( + (cookie) => cookie.isBlocked + ), + }, + }; + + return _data; + }); + }, [ + completeJson, + cookiesWithIssues, + isKeySelected, + reshapedCookies, + setSidebarData, + siteFilteredCookies, + siteFilteredTechnologies, + sites, + ]); + + useEffect(() => { + if (selectedItemKey === null && Object.keys(sidebarData).length > 0) { + updateSelectedItemKey(SIDEBAR_ITEMS_KEYS.COOKIES); + } + }, [isKeySelected, selectedItemKey, sidebarData, updateSelectedItemKey]); + + return ( +
+ + + +
+ {PanelElement && } +
+
+ ); +}; + +export default Layout; diff --git a/packages/cli-dashboard/src/components/siteMapReport/sidebarData.tsx b/packages/cli-dashboard/src/components/siteMapReport/sidebarData.ts similarity index 68% rename from packages/cli-dashboard/src/components/siteMapReport/sidebarData.tsx rename to packages/cli-dashboard/src/components/siteMapReport/sidebarData.ts index a1b143d90..6f3b8a2e8 100644 --- a/packages/cli-dashboard/src/components/siteMapReport/sidebarData.tsx +++ b/packages/cli-dashboard/src/components/siteMapReport/sidebarData.ts @@ -16,23 +16,33 @@ /** * External dependencies */ -import React from 'react'; import { - CookieIcon, - CookieIconWhite, + SIDEBAR_ITEMS_KEYS, + WarningBare, type SidebarItems, } from '@ps-analysis-tool/design-system'; const sidebarData: SidebarItems = { - 'sitemap-landing-page': { + [SIDEBAR_ITEMS_KEYS.COOKIES]: { title: 'Sitemap Report', children: {}, + dropdownOpen: true, }, - 'sitemap-affected-cookies': { - title: 'Affected Cookies', + [SIDEBAR_ITEMS_KEYS.COOKIES_WITH_ISSUES]: { + title: 'Cookie Issues', children: {}, - icon: , - selectedIcon: , + icon: { + Element: WarningBare, + props: { + className: 'fill-granite-gray', + }, + }, + selectedIcon: { + Element: WarningBare, + props: { + className: 'fill-white', + }, + }, }, }; diff --git a/packages/cli-dashboard/src/components/siteMapReport/sitemapAffectedCookies.tsx b/packages/cli-dashboard/src/components/siteMapReport/sitemapCookiesWithIssues.tsx similarity index 73% rename from packages/cli-dashboard/src/components/siteMapReport/sitemapAffectedCookies.tsx rename to packages/cli-dashboard/src/components/siteMapReport/sitemapCookiesWithIssues.tsx index 646116f50..ffa2c023c 100644 --- a/packages/cli-dashboard/src/components/siteMapReport/sitemapAffectedCookies.tsx +++ b/packages/cli-dashboard/src/components/siteMapReport/sitemapCookiesWithIssues.tsx @@ -23,14 +23,16 @@ import type { CookieTableData } from '@ps-analysis-tool/common'; /** * Internal dependencies. */ -import AffectedCookies from '../affectedCookies'; +import CookiesWithIssues from '../cookiesWithIssues'; -interface SiteMapAffectedCookiesProps { +interface SiteMapCookiesWithIssuesProps { cookies: CookieTableData[]; } -const SiteMapAffectedCookies = ({ cookies }: SiteMapAffectedCookiesProps) => { - return ; +const SiteMapCookiesWithIssues = ({ + cookies, +}: SiteMapCookiesWithIssuesProps) => { + return ; }; -export default SiteMapAffectedCookies; +export default SiteMapCookiesWithIssues; diff --git a/packages/cli-dashboard/src/components/siteReport/components/layout.tsx b/packages/cli-dashboard/src/components/siteReport/components/layout.tsx index ed5d019eb..992d67328 100644 --- a/packages/cli-dashboard/src/components/siteReport/components/layout.tsx +++ b/packages/cli-dashboard/src/components/siteReport/components/layout.tsx @@ -16,7 +16,7 @@ /** * External dependencies. */ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef } from 'react'; import { Resizable } from 're-resizable'; import { CookieIcon, @@ -26,24 +26,24 @@ import { type SidebarItems, SiteBoundariesIcon, SiteBoundariesIconWhite, + SIDEBAR_ITEMS_KEYS, } from '@ps-analysis-tool/design-system'; +import { UNKNOWN_FRAME_KEY } from '@ps-analysis-tool/common'; /** * Internal dependencies. */ import { useContentStore } from '../stateProviders/contentStore'; -import { UNKNOWN_FRAME_KEY } from '@ps-analysis-tool/common'; -import TABS from '../tabs'; import CookiesTab from '../tabs/cookies'; -import SiteAffectedCookies from '../tabs/siteAffectedCookies'; +import SiteCookiesWithIssues from '../tabs/siteCookiesWithIssues'; import Technologies from '../tabs/technologies'; interface LayoutProps { selectedSite: string | null; + setSidebarData: React.Dispatch>; } -const Layout = ({ selectedSite }: LayoutProps) => { - const [data, setData] = useState(TABS); +const Layout = ({ selectedSite, setSidebarData }: LayoutProps) => { const { tabCookies, technologies } = useContentStore(({ state }) => ({ tabCookies: state.tabCookies, technologies: state.technologies, @@ -65,46 +65,52 @@ const Layout = ({ selectedSite }: LayoutProps) => { [tabCookies] ); - const { - activePanel, - selectedItemKey, - sidebarItems, - isSidebarFocused, - setIsSidebarFocused, - updateSelectedItemKey, - onKeyNavigation, - toggleDropdown, - isKeyAncestor, - isKeySelected, - } = useSidebar({ data }); + const { activePanel, selectedItemKey, updateSelectedItemKey } = useSidebar( + ({ state, actions }) => ({ + activePanel: state.activePanel, + selectedItemKey: state.selectedItemKey, + updateSelectedItemKey: actions.updateSelectedItemKey, + }) + ); + + const { Element: PanelElement, props } = activePanel.panel; useEffect(() => { - setData((prev) => { + setSidebarData((prev) => { const _data = { ...prev }; const keys = selectedItemKey?.split('#') ?? []; - _data['cookies'].panel = ( - - ); + _data[SIDEBAR_ITEMS_KEYS.COOKIES].panel = { + Element: CookiesTab, + props: { + selectedFrameUrl: null, + selectedSite, + }, + }; const selectedFrameUrl = frameUrls.find( (url) => url === keys[keys.length - 1] ); - _data['cookies'].children = frameUrls.reduce( + _data[SIDEBAR_ITEMS_KEYS.COOKIES].children = frameUrls.reduce( (acc: SidebarItems, url: string): SidebarItems => { acc[url] = { title: url, - panel: ( - - ), + panel: { + Element: CookiesTab, + props: { + selectedFrameUrl, + selectedSite, + }, + }, children: {}, - icon: , - selectedIcon: , + icon: { + Element: CookieIcon, + }, + selectedIcon: { + Element: CookieIconWhite, + }, }; return acc; @@ -112,29 +118,41 @@ const Layout = ({ selectedSite }: LayoutProps) => { {} ); - _data['affected-cookies'].panel = ( - - ); + _data[SIDEBAR_ITEMS_KEYS.COOKIES_WITH_ISSUES].panel = { + Element: SiteCookiesWithIssues, + props: { + selectedSite, + }, + }; if (technologies && technologies.length > 0) { - _data['technologies'] = { + _data[SIDEBAR_ITEMS_KEYS.TECHNOLOGIES] = { title: 'Technologies', children: {}, - icon: , - selectedIcon: , - panel: , + icon: { + Element: SiteBoundariesIcon, + }, + selectedIcon: { + Element: SiteBoundariesIconWhite, + }, + panel: { + Element: Technologies, + props: { + selectedSite, + }, + }, }; } else { - delete _data['technologies']; + delete _data[SIDEBAR_ITEMS_KEYS.TECHNOLOGIES]; } return _data; }); - }, [frameUrls, selectedItemKey, selectedSite, technologies]); + }, [frameUrls, selectedItemKey, selectedSite, setSidebarData, technologies]); useEffect(() => { if (selectedItemKey === null) { - updateSelectedItemKey('cookies'); + updateSelectedItemKey(SIDEBAR_ITEMS_KEYS.COOKIES); } }, [selectedItemKey, updateSelectedItemKey]); @@ -142,7 +160,7 @@ const Layout = ({ selectedSite }: LayoutProps) => { useEffect(() => { if (selectedSite !== lastSelectedSite.current) { - updateSelectedItemKey('cookies'); + updateSelectedItemKey(SIDEBAR_ITEMS_KEYS.COOKIES); lastSelectedSite.current = selectedSite; } }, [selectedSite, updateSelectedItemKey]); @@ -157,19 +175,14 @@ const Layout = ({ selectedSite }: LayoutProps) => { right: true, }} > - + -
{activePanel}
+
+ {PanelElement && } +
); }; diff --git a/packages/cli-dashboard/src/components/siteReport/index.tsx b/packages/cli-dashboard/src/components/siteReport/index.tsx index bcb710bd3..6299268bf 100644 --- a/packages/cli-dashboard/src/components/siteReport/index.tsx +++ b/packages/cli-dashboard/src/components/siteReport/index.tsx @@ -17,18 +17,23 @@ /** * External dependencies */ -import React from 'react'; +import React, { useState } from 'react'; import type { CompleteJson, CookieJsonDataType, TechnologyData, } from '@ps-analysis-tool/common'; +import { + SidebarProvider, + type SidebarItems, +} from '@ps-analysis-tool/design-system'; /** * Internal dependencies. */ import { Provider as ContentStoreProvider } from './stateProviders/contentStore'; import Layout from './components/layout'; +import Tabs from './tabs'; interface SiteReportProps { cookies: { @@ -47,13 +52,17 @@ const SiteReport = ({ completeJson, selectedSite, }: SiteReportProps) => { + const [data, setData] = useState(Tabs); + return ( - + + + ); }; diff --git a/packages/cli-dashboard/src/components/siteReport/stateProviders/contentStore/index.tsx b/packages/cli-dashboard/src/components/siteReport/stateProviders/contentStore/index.tsx index 638344766..71544cbd8 100644 --- a/packages/cli-dashboard/src/components/siteReport/stateProviders/contentStore/index.tsx +++ b/packages/cli-dashboard/src/components/siteReport/stateProviders/contentStore/index.tsx @@ -17,12 +17,13 @@ * External dependencies. */ import React, { type PropsWithChildren, useMemo } from 'react'; -import { useContextSelector, createContext } from 'use-context-selector'; import { type CompleteJson, type CookieJsonDataType, type CookieTableData, type TechnologyData, + useContextSelector, + createContext, } from '@ps-analysis-tool/common'; /** diff --git a/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesLandingContainer/cookieLanding/blockedCookiesSection.tsx b/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesLandingContainer/cookieLanding/blockedCookiesSection.tsx new file mode 100644 index 000000000..a9c6c7d8e --- /dev/null +++ b/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesLandingContainer/cookieLanding/blockedCookiesSection.tsx @@ -0,0 +1,106 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * External dependencies + */ +import React from 'react'; +import { + CookiesLandingWrapper, + type DataMapping, + prepareCookieStatsComponents, + prepareCookiesCount, + MatrixContainer, + type MatrixComponentProps, + LEGEND_DESCRIPTION, + useFiltersMapping, +} from '@ps-analysis-tool/design-system'; +import type { TabCookies, TabFrames } from '@ps-analysis-tool/common'; + +interface BlockedCookiesSectionProps { + tabCookies: TabCookies | null; + cookiesWithIssues: TabCookies | null; + tabFrames: TabFrames | null; +} + +const BlockedCookiesSection = ({ + tabCookies, + cookiesWithIssues, + tabFrames, +}: BlockedCookiesSectionProps) => { + const { selectedItemUpdater } = useFiltersMapping(tabFrames || {}); + const cookieStats = prepareCookiesCount(tabCookies); + const cookiesStatsComponents = prepareCookieStatsComponents(cookieStats); + const blockedCookieDataMapping: DataMapping[] = [ + { + title: 'Blocked cookies', + count: cookieStats.blockedCookies.total, + data: cookiesStatsComponents.blocked, + onClick: () => selectedItemUpdater('All', 'blockedReasons'), + }, + ]; + const dataComponents: MatrixComponentProps[] = + cookiesStatsComponents.blockedCookiesLegend.map((component) => { + const legendDescription = LEGEND_DESCRIPTION[component.label] || ''; + return { + ...component, + description: legendDescription, + title: component.label, + containerClasses: '', + onClick: (title: string) => + selectedItemUpdater(title, 'blockedReasons'), + }; + }); + + const blockedCookiesStats = prepareCookiesCount(cookiesWithIssues); + const blockedCookiesStatsComponents = + prepareCookieStatsComponents(blockedCookiesStats); + const blockedDataComponents: MatrixComponentProps[] = + blockedCookiesStatsComponents.legend.map((component) => { + const legendDescription = LEGEND_DESCRIPTION[component.label] || ''; + return { + ...component, + description: legendDescription, + title: component.label, + containerClasses: '', + }; + }); + + return ( + + {dataComponents.length > 0 && ( + <> + +
+
+ +
+
+ + )} +
+ ); +}; +export default BlockedCookiesSection; diff --git a/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesLandingContainer/cookieLanding/cookiesSection.tsx b/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesLandingContainer/cookieLanding/cookiesSection.tsx new file mode 100644 index 000000000..a595afa7c --- /dev/null +++ b/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesLandingContainer/cookieLanding/cookiesSection.tsx @@ -0,0 +1,79 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * External dependencies + */ +import React, { useMemo } from 'react'; +import { + CookiesLandingWrapper, + CookiesMatrix, + MessageBox, + prepareCookieDataMapping, + prepareCookieStatsComponents, + prepareCookiesCount, + useFiltersMapping, +} from '@ps-analysis-tool/design-system'; +import type { TabCookies, TabFrames } from '@ps-analysis-tool/common'; +/** + * Internal dependencies + */ + +interface CookiesSectionProps { + tabCookies: TabCookies | null; + tabFrames: TabFrames | null; +} +const CookiesSection = ({ tabCookies, tabFrames }: CookiesSectionProps) => { + const { selectedItemUpdater } = useFiltersMapping(tabFrames || {}); + + const cookieStats = prepareCookiesCount(tabCookies); + const cookiesStatsComponents = prepareCookieStatsComponents(cookieStats); + const cookieClassificationDataMapping = prepareCookieDataMapping( + cookieStats, + cookiesStatsComponents, + selectedItemUpdater + ); + + const cookieComponentData = useMemo(() => { + return cookiesStatsComponents.legend.map((component) => ({ + ...component, + onClick: (title: string) => + selectedItemUpdater(title, 'analytics.category'), + })); + }, [cookiesStatsComponents.legend, selectedItemUpdater]); + + return ( + + {!cookieStats || + (cookieStats?.firstParty.total === 0 && + cookieStats?.thirdParty.total === 0 && ( + + ))} + + + ); +}; +export default CookiesSection; diff --git a/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesLandingContainer/cookieLanding/index.ts b/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesLandingContainer/cookieLanding/index.ts new file mode 100644 index 000000000..50e480013 --- /dev/null +++ b/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesLandingContainer/cookieLanding/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { default as CookiesSection } from './cookiesSection'; +export { default as BlockedCookiesSection } from './blockedCookiesSection'; diff --git a/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesLandingContainer/index.tsx b/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesLandingContainer/index.tsx index 8d45e44d9..1fb51a9f7 100644 --- a/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesLandingContainer/index.tsx +++ b/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesLandingContainer/index.tsx @@ -17,67 +17,78 @@ /** * External dependencies. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { - Button, CookiesLanding, - CookiesMatrix, - prepareCookiesCount, - prepareCookieStatsComponents, + MenuBar, + type CookiesLandingSection, + type MenuData, } from '@ps-analysis-tool/design-system'; import type { TabCookies, TabFrames } from '@ps-analysis-tool/common'; +import CookiesSection from './cookieLanding/cookiesSection'; +import BlockedCookiesSection from './cookieLanding/blockedCookiesSection'; interface CookiesLandingContainerProps { tabFrames: TabFrames; tabCookies: TabCookies; - affectedCookies: TabCookies; + cookiesWithIssues: TabCookies; downloadReport?: () => void; } const CookiesLandingContainer = ({ tabFrames, tabCookies, - affectedCookies, + cookiesWithIssues, downloadReport, }: CookiesLandingContainerProps) => { + const sections: Array = useMemo( + () => [ + { + name: 'Cookies', + link: 'cookies', + panel: { + Element: CookiesSection, + props: { + tabCookies, + tabFrames, + }, + }, + }, + { + name: 'Blocked Cookies', + link: 'blocked-cookies', + panel: { + Element: BlockedCookiesSection, + props: { + tabCookies, + cookiesWithIssues, + tabFrames, + }, + }, + }, + ], + [tabCookies, tabFrames, cookiesWithIssues] + ); + + const menuData: MenuData = useMemo( + () => sections.map(({ name, link }) => ({ name, link })), + [sections] + ); + return ( <> - {downloadReport && ( -
-
- )} - -
-
- + + + {sections.map(({ link, panel: { Element, props } }) => ( +
+ {Element && }
-
+ ))} ); diff --git a/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesListing/index.tsx b/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesListing/index.tsx index ce9893bcb..4e4bf7006 100644 --- a/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesListing/index.tsx +++ b/packages/cli-dashboard/src/components/siteReport/tabs/cookies/cookiesListing/index.tsx @@ -25,7 +25,7 @@ import type { CookieTableData } from '@ps-analysis-tool/common'; /** * Internal dependencies */ -import useCookieListing from '../../../../../hooks/useCookieListing.tsx'; +import useCookieListing from '../../../../../hooks/useCookieListing'; import { useContentStore } from '../../../stateProviders/contentStore'; /** @@ -57,13 +57,18 @@ const CookiesListing = ({ [tabCookies, selectedFrameUrl] ); - const { tableColumns, filters, searchKeys, tablePersistentSettingsKey } = - useCookieListing( - Object.values(tabCookies), - selectedFrameUrl, - 'cookiesListing', - selectedSite - ); + const { + tableColumns, + filters, + searchKeys, + tablePersistentSettingsKey, + isSidebarOpen, + } = useCookieListing( + Object.values(tabCookies), + selectedFrameUrl, + 'cookiesListing', + selectedSite + ); return (
@@ -80,21 +85,19 @@ const CookiesListing = ({ className="h-full flex" >
diff --git a/packages/cli-dashboard/src/components/siteReport/tabs/cookies/index.tsx b/packages/cli-dashboard/src/components/siteReport/tabs/cookies/index.tsx index 94aec4e6f..33c43ac2b 100644 --- a/packages/cli-dashboard/src/components/siteReport/tabs/cookies/index.tsx +++ b/packages/cli-dashboard/src/components/siteReport/tabs/cookies/index.tsx @@ -48,7 +48,7 @@ const CookiesTab = ({ selectedFrameUrl, selectedSite }: CookiesTabProps) => { [tabCookies] ); - const affectedCookies = useMemo( + const cookiesWithIssues = useMemo( () => Object.fromEntries( Object.entries(tabCookies).filter(([, cookie]) => cookie.isBlocked) @@ -78,7 +78,7 @@ const CookiesTab = ({ selectedFrameUrl, selectedSite }: CookiesTabProps) => {
diff --git a/packages/cli-dashboard/src/components/siteReport/tabs/index.tsx b/packages/cli-dashboard/src/components/siteReport/tabs/index.ts similarity index 64% rename from packages/cli-dashboard/src/components/siteReport/tabs/index.tsx rename to packages/cli-dashboard/src/components/siteReport/tabs/index.ts index d71a4c481..4e98a7a7b 100644 --- a/packages/cli-dashboard/src/components/siteReport/tabs/index.tsx +++ b/packages/cli-dashboard/src/components/siteReport/tabs/index.ts @@ -16,25 +16,40 @@ /** * External dependencies. */ -import React from 'react'; import { CookieIcon, CookieIconWhite, + SIDEBAR_ITEMS_KEYS, + WarningBare, type SidebarItems, } from '@ps-analysis-tool/design-system'; const Tabs: SidebarItems = { - cookies: { + [SIDEBAR_ITEMS_KEYS.COOKIES]: { title: 'Cookies', children: {}, - icon: , - selectedIcon: , + icon: { + Element: CookieIcon, + }, + selectedIcon: { + Element: CookieIconWhite, + }, }, - 'affected-cookies': { - title: 'Affected Cookies', + [SIDEBAR_ITEMS_KEYS.COOKIES_WITH_ISSUES]: { + title: 'Cookie Issues', children: {}, - icon: , - selectedIcon: , + icon: { + Element: WarningBare, + props: { + className: 'fill-granite-gray', + }, + }, + selectedIcon: { + Element: WarningBare, + props: { + className: 'fill-white', + }, + }, }, }; diff --git a/packages/cli-dashboard/src/components/siteReport/tabs/siteAffectedCookies/index.tsx b/packages/cli-dashboard/src/components/siteReport/tabs/siteCookiesWithIssues/index.tsx similarity index 76% rename from packages/cli-dashboard/src/components/siteReport/tabs/siteAffectedCookies/index.tsx rename to packages/cli-dashboard/src/components/siteReport/tabs/siteCookiesWithIssues/index.tsx index 4c8ab29e6..29de488a4 100644 --- a/packages/cli-dashboard/src/components/siteReport/tabs/siteAffectedCookies/index.tsx +++ b/packages/cli-dashboard/src/components/siteReport/tabs/siteCookiesWithIssues/index.tsx @@ -22,21 +22,23 @@ import React from 'react'; /** * Internal dependencies */ -import AffectedCookies from '../../../affectedCookies'; +import CookiesWithIssues from '../../../cookiesWithIssues'; import { useContentStore } from '../../stateProviders/contentStore'; -interface SiteAffectedCookiesProps { +interface SiteCookiesWithIssuesProps { selectedSite: string | null; } -const SiteAffectedCookies = ({ selectedSite }: SiteAffectedCookiesProps) => { +const SiteCookiesWithIssues = ({ + selectedSite, +}: SiteCookiesWithIssuesProps) => { const { tabCookies } = useContentStore(({ state }) => ({ tabCookies: Object.values(state.tabCookies).filter( (cookie) => cookie.isBlocked ), })); - return ; + return ; }; -export default SiteAffectedCookies; +export default SiteCookiesWithIssues; diff --git a/packages/cli-dashboard/src/components/siteReport/tabs/technologies/index.tsx b/packages/cli-dashboard/src/components/siteReport/tabs/technologies/index.tsx index e87d05008..416e0d67d 100644 --- a/packages/cli-dashboard/src/components/siteReport/tabs/technologies/index.tsx +++ b/packages/cli-dashboard/src/components/siteReport/tabs/technologies/index.tsx @@ -21,13 +21,13 @@ import React, { useMemo, useState } from 'react'; import { Resizable } from 're-resizable'; import { Table, - useTable, type TableColumn, type InfoType, type TableRow, type TableFilter, + TableProvider, } from '@ps-analysis-tool/design-system'; -import type { TechnologyData } from '@ps-analysis-tool/common'; +import { noop, type TechnologyData } from '@ps-analysis-tool/common'; /** * Internal dependencies @@ -94,14 +94,6 @@ const Technologies = ({ selectedSite }: TechnologiesProps) => { return 'technologyListing'; }, [selectedSite]); - const table = useTable({ - data, - tableColumns, - tableFilterData: filters, - tableSearchKeys: searchKeys, - tablePersistentSettingsKey, - }); - return (
{ }} className="h-full flex" > - { setSelectedRow(row as TechnologyData); }} + onRowContextMenu={noop} getRowObjectKey={(row: TableRow) => { return (row.originalData as TechnologyData).slug; }} - /> + > +
+
{selectedRow ? ( diff --git a/packages/cli-dashboard/src/components/utils/reportDownloader/generateSiteMapReportandDownload.ts b/packages/cli-dashboard/src/components/utils/reportDownloader/generateSiteMapReportandDownload.ts index 6b599d45f..2349b811b 100644 --- a/packages/cli-dashboard/src/components/utils/reportDownloader/generateSiteMapReportandDownload.ts +++ b/packages/cli-dashboard/src/components/utils/reportDownloader/generateSiteMapReportandDownload.ts @@ -31,10 +31,18 @@ const generateSiteMapReportandDownload = async (JSONReport: CompleteJson[]) => { return; } + const today = new Date(); + + const day = String(today.getDate()).padStart(2, '0'); // Get the day and ensure it has leading zero if needed + const month = String(today.getMonth() + 1).padStart(2, '0'); // Get the month and ensure it has leading zero if needed + const year = today.getFullYear(); + const zip = new JSZip(); JSONReport.forEach((data) => { - const zipFolder: JSZip | null = zip.folder(getFolderName(data.pageUrl)); + const zipFolder: JSZip | null = zip.folder( + `psat_cli_report_${getFolderName(data.pageUrl)}_${day + month + year}` + ); if (!zipFolder) { return; @@ -44,7 +52,12 @@ const generateSiteMapReportandDownload = async (JSONReport: CompleteJson[]) => { }); const content = await zip.generateAsync({ type: 'blob' }); - saveAs(content, 'report.zip'); + saveAs( + content, + `psat_cli_report_${getFolderName(JSONReport[0].pageUrl)}_${ + day + month + year + }.zip` + ); }; export default generateSiteMapReportandDownload; diff --git a/packages/cli-dashboard/src/components/utils/reportDownloader/generateSiteReportandDownload.ts b/packages/cli-dashboard/src/components/utils/reportDownloader/generateSiteReportandDownload.ts index 8ea038705..5416e6215 100644 --- a/packages/cli-dashboard/src/components/utils/reportDownloader/generateSiteReportandDownload.ts +++ b/packages/cli-dashboard/src/components/utils/reportDownloader/generateSiteReportandDownload.ts @@ -33,6 +33,12 @@ const generateSiteReportandDownload = async ( return; } + const today = new Date(); + + const day = String(today.getDate()).padStart(2, '0'); // Get the day and ensure it has leading zero if needed + const month = String(today.getMonth() + 1).padStart(2, '0'); // Get the month and ensure it has leading zero if needed + const year = today.getFullYear(); + const zip = new JSZip(); let siteAnalysisData: CompleteJson; @@ -46,7 +52,9 @@ const generateSiteReportandDownload = async ( } const zipFolder: JSZip | null = zip.folder( - getFolderName(siteAnalysisData.pageUrl) + `psat_cli_report_${getFolderName(JSONReport[0].pageUrl)}_${ + day + month + year + }` ); if (!zipFolder) { @@ -56,7 +64,12 @@ const generateSiteReportandDownload = async ( createZip(siteAnalysisData, zipFolder); const content = await zip.generateAsync({ type: 'blob' }); - saveAs(content, 'report.zip'); + saveAs( + content, + `psat_cli_report_${getFolderName(JSONReport[0].pageUrl)}_${ + day + month + year + }.zip` + ); }; export default generateSiteReportandDownload; diff --git a/packages/cli-dashboard/src/components/utils/reportDownloader/utils.ts b/packages/cli-dashboard/src/components/utils/reportDownloader/utils.ts index eb033c3ae..fa94bf39e 100644 --- a/packages/cli-dashboard/src/components/utils/reportDownloader/utils.ts +++ b/packages/cli-dashboard/src/components/utils/reportDownloader/utils.ts @@ -19,7 +19,7 @@ */ import type JSZip from 'jszip'; import { - generateAffectedCookiesCSV, + generateCookiesWithIssuesCSV, generateAllCookiesCSV, generateSummaryDataCSV, generateTechnologyCSV, @@ -32,13 +32,13 @@ const generateCSVFiles = (data: CompleteJson) => { if (data.technologyData.length > 0) { technologyDataCSV = generateTechnologyCSV(data); } - const affectedCookiesDataCSV = generateAffectedCookiesCSV(data); + const cookiesWithIssuesDataCSV = generateCookiesWithIssuesCSV(data); const summaryDataCSV = generateSummaryDataCSV(data); return { allCookiesCSV, technologyDataCSV, - affectedCookiesDataCSV, + cookiesWithIssuesDataCSV, summaryDataCSV, }; }; @@ -47,7 +47,7 @@ export const createZip = (analysisData: CompleteJson, zipObject: JSZip) => { const { allCookiesCSV, technologyDataCSV, - affectedCookiesDataCSV, + cookiesWithIssuesDataCSV, summaryDataCSV, } = generateCSVFiles(analysisData); @@ -55,7 +55,7 @@ export const createZip = (analysisData: CompleteJson, zipObject: JSZip) => { if (technologyDataCSV) { zipObject.file('technologies.csv', technologyDataCSV); } - zipObject.file('affected-cookies.csv', affectedCookiesDataCSV); + zipObject.file('cookie-issues.csv', cookiesWithIssuesDataCSV); zipObject.file('report.csv', summaryDataCSV); zipObject.file('report.json', JSON.stringify(analysisData, null, 4)); }; @@ -64,7 +64,8 @@ export const getFolderName = (pageUrl: string) => { let folderName = pageUrl .trim() .replace(/^https?:\/\//, '') - .replace(/\/+/g, '-'); + .replace(/\/+/g, '-') + .replace(/\./g, '-'); if (folderName.endsWith('-')) { const lastDashIndex = folderName.lastIndexOf('-'); diff --git a/packages/cli-dashboard/src/hooks/useCookieListing.tsx/utils/calculateBlockedReasonsFilterValues.tsx b/packages/cli-dashboard/src/hooks/useCookieListing.tsx/utils/calculateBlockedReasonsFilterValues.tsx deleted file mode 100644 index 6af116efb..000000000 --- a/packages/cli-dashboard/src/hooks/useCookieListing.tsx/utils/calculateBlockedReasonsFilterValues.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * External dependencies - */ -import { getValueByKey, type CookieTableData } from '@ps-analysis-tool/common'; -import type { TableFilter } from '@ps-analysis-tool/design-system'; - -const calculateBlockedReasonsFilterValues = (tabCookies: CookieTableData[]) => { - return tabCookies.reduce( - (acc, cookie) => { - const blockedReason = getValueByKey('blockedReasons', cookie); - - if (!cookie.frameIdList || cookie?.frameIdList?.length === 0) { - return acc; - } - - blockedReason?.forEach((reason: string) => { - if (!acc) { - acc = {}; - } - - acc[reason] = { - selected: false, - }; - }); - - return acc; - }, - {} - ); -}; - -export default calculateBlockedReasonsFilterValues; diff --git a/packages/cli-dashboard/src/hooks/useCookieListing.tsx/utils/calculateDynamicFilterValues.ts b/packages/cli-dashboard/src/hooks/useCookieListing.tsx/utils/calculateDynamicFilterValues.ts deleted file mode 100644 index abc6cfda1..000000000 --- a/packages/cli-dashboard/src/hooks/useCookieListing.tsx/utils/calculateDynamicFilterValues.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * External dependencies - */ -import { getValueByKey, type CookieTableData } from '@ps-analysis-tool/common'; -import type { TableFilter } from '@ps-analysis-tool/design-system'; - -const calculateDynamicFilterValues = ( - key: string, - tabCookies: CookieTableData[] -): TableFilter[keyof TableFilter]['filterValues'] => { - return tabCookies.reduce( - (acc, cookie) => { - const value = getValueByKey(key, cookie); - - if (!acc) { - acc = {}; - } - - if (value) { - acc[value] = { - selected: false, - }; - } - - return acc; - }, - {} - ); -}; - -export default calculateDynamicFilterValues; diff --git a/packages/cli-dashboard/src/hooks/useCookieListing.tsx/index.tsx b/packages/cli-dashboard/src/hooks/useCookieListing/index.tsx similarity index 84% rename from packages/cli-dashboard/src/hooks/useCookieListing.tsx/index.tsx rename to packages/cli-dashboard/src/hooks/useCookieListing/index.tsx index d9117dbbd..928b81314 100644 --- a/packages/cli-dashboard/src/hooks/useCookieListing.tsx/index.tsx +++ b/packages/cli-dashboard/src/hooks/useCookieListing/index.tsx @@ -18,28 +18,39 @@ * External dependencies */ import React, { useMemo } from 'react'; -import type { - InfoType, - TableColumn, - TableFilter, +import { + useSidebar, + type InfoType, + type TableColumn, + type TableFilter, + calculateBlockedReasonsFilterValues, + calculateDynamicFilterValues, + evaluateSelectAllOption, + evaluateStaticFilterValues, } from '@ps-analysis-tool/design-system'; import { calculateEffectiveExpiryDate, type CookieTableData, } from '@ps-analysis-tool/common'; -/** - * Internal dependencies - */ -import calculateDynamicFilterValues from './utils/calculateDynamicFilterValues'; -import calculateBlockedReasonsFilterValues from './utils/calculateBlockedReasonsFilterValues'; - const useCookieListing = ( tabCookies: CookieTableData[], selectedFrameUrl: string, persistenceKey = 'cookiesListing', selectedSite?: string | null ) => { + const { activePanelQuery, clearActivePanelQuery } = useSidebar( + ({ state }) => ({ + activePanelQuery: state.activePanel.query, + clearActivePanelQuery: state.activePanel.clearQuery, + }) + ); + + const parsedQuery = useMemo( + () => JSON.parse(activePanelQuery || '{}'), + [activePanelQuery] + ); + const tableColumns = useMemo( () => [ { @@ -140,7 +151,9 @@ const useCookieListing = ( hasPrecalculatedFilterValues: true, filterValues: calculateDynamicFilterValues( 'analytics.category', - tabCookies + tabCookies, + parsedQuery?.filter?.['analytics.category'], + clearActivePanelQuery ), sortValues: true, useGenericPersistenceKey: true, @@ -148,14 +161,20 @@ const useCookieListing = ( isFirstParty: { title: 'Scope', hasStaticFilterValues: true, - filterValues: { - 'First Party': { - selected: false, - }, - 'Third Party': { - selected: false, + hasPrecalculatedFilterValues: true, + filterValues: evaluateStaticFilterValues( + { + 'First Party': { + selected: false, + }, + 'Third Party': { + selected: false, + }, }, - }, + 'isFirstParty', + parsedQuery, + clearActivePanelQuery + ), useGenericPersistenceKey: true, comparator: (value: InfoType, filterValue: string) => { const val = Boolean(value); @@ -277,7 +296,9 @@ const useCookieListing = ( hasPrecalculatedFilterValues: true, filterValues: calculateDynamicFilterValues( 'analytics.platform', - tabCookies + tabCookies, + parsedQuery?.filter?.['analytics.platform'], + clearActivePanelQuery ), sortValues: true, useGenericPersistenceKey: true, @@ -287,7 +308,16 @@ const useCookieListing = ( hasStaticFilterValues: true, hasPrecalculatedFilterValues: true, enableSelectAllOption: true, - filterValues: calculateBlockedReasonsFilterValues(tabCookies), + isSelectAllOptionSelected: evaluateSelectAllOption( + 'blockedReasons', + parsedQuery, + clearActivePanelQuery + ), + filterValues: calculateBlockedReasonsFilterValues( + tabCookies, + parsedQuery?.filter?.blockedReasons, + clearActivePanelQuery + ), sortValues: true, useGenericPersistenceKey: true, comparator: (value: InfoType, filterValue: string) => { @@ -295,7 +325,7 @@ const useCookieListing = ( }, }, }), - [tabCookies] + [clearActivePanelQuery, parsedQuery, tabCookies] ); const searchKeys = useMemo( @@ -316,6 +346,7 @@ const useCookieListing = ( filters, searchKeys, tablePersistentSettingsKey, + isSidebarOpen: parsedQuery?.filter ? true : false, }; }; diff --git a/packages/cli/package.json b/packages/cli/package.json index 6f13fd66e..c6c0f90c1 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@ps-analysis-tool/cli", - "version": "0.6.0", + "version": "0.7.0", "description": "CLI tool for analysis", "main": "index.js", "scripts": { diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index e45f06ece..90ccd32e3 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -46,7 +46,7 @@ const DELAY_TIME = 20000; const program = new Command(); program - .version('0.6.0') + .version('0.7.0') .description('CLI to test a URL for 3p cookies') .option('-u, --url ', 'URL of a site') .option('-s, --sitemap-url ', 'URL of a sitemap') @@ -55,6 +55,7 @@ program '-p, --sitemap-path ', 'Path to a sitemap saved in the file system' ) + .option('-po, --port ', 'A port for the CLI dashboard server.') .option('-ul, --url-limit ', 'No of URLs to analyze') .option( '-nh, --no-headless ', @@ -68,23 +69,14 @@ program .option( '-d, --out-dir ', 'Directory path where the analysis data will be stored' + ) + .option( + '-ab, --accept-banner', + 'This will accept the GDPR banner if present.' ); program.parse(); -const initialize = async () => { - //check if devserver port in already in use - - const portInUse = await checkPortInUse(9000); - - if (portInUse) { - console.error( - 'Error: Report server port (9000) already in use. You might be already running CLI' - ); - process.exit(1); - } -}; - const saveResults = async ( outDir: string, result: CompleteJson | CompleteJson[] @@ -93,29 +85,27 @@ const saveResults = async ( await writeFile(outDir + '/out.json', JSON.stringify(result, null, 4)); }; -const startDashboardServer = async (dir: string) => { - exec('npm run cli-dashboard:dev'); +const startDashboardServer = async (dir: string, port: number) => { + exec(`npm run cli-dashboard:dev -- -- --port ${port}`); await delay(2000); - console.log( - `Report is being served at the URL: http://localhost:9000?dir=${dir}` - ); + console.log(`Report: http://localhost:${port}?dir=${dir}`); }; // eslint-disable-next-line complexity (async () => { - await initialize(); - const url = program.opts().url; const sitemapUrl = program.opts().sitemapUrl; const csvPath = program.opts().csvPath; const sitemapPath = program.opts().sitemapPath; + const port = parseInt(program.opts().port || '9000'); const numberOfUrlsInput = program.opts().urlLimit; const isHeadless = Boolean(program.opts().headless); const shouldSkipPrompts = !program.opts().prompts; const shouldSkipTechnologyAnalysis = !program.opts().technology; const outDir = program.opts().outDir; + const shouldSkipAcceptBanner = program.opts().acceptBanner; validateArgs( url, @@ -123,9 +113,23 @@ const startDashboardServer = async (dir: string) => { csvPath, sitemapPath, numberOfUrlsInput, - outDir + outDir, + port ); + //check if devserver port in already in use only if the dashboard is goint to be used + + if (!outDir) { + const isPortInUse = await checkPortInUse(port); + + if (isPortInUse) { + console.error( + `Error: Report server port ${port} already in use. You might be already running CLI` + ); + process.exit(1); + } + } + const prefix = url || sitemapUrl ? Utility.generatePrefix(url || sitemapUrl) @@ -178,7 +182,7 @@ const startDashboardServer = async (dir: string) => { const cookieDictionary = await fetchDictionary(); spinnies.add('cookie-spinner', { - text: 'Analysing cookies on first page visit', + text: 'Analysing cookies on first site visit', }); const cookieAnalysisData = await analyzeCookiesUrlsInBatches( @@ -187,7 +191,8 @@ const startDashboardServer = async (dir: string) => { DELAY_TIME, cookieDictionary, 3, - urlsToProcess.length !== 1 ? spinnies : undefined + urlsToProcess.length !== 1 ? spinnies : undefined, + shouldSkipAcceptBanner ); spinnies.succeed('cookie-spinner', { @@ -229,6 +234,7 @@ const startDashboardServer = async (dir: string) => { startDashboardServer( encodeURIComponent(prefix) + - (sitemapUrl || csvPath || sitemapPath ? '&type=sitemap' : '') + (sitemapUrl || csvPath || sitemapPath ? '&type=sitemap' : ''), + port ); })(); diff --git a/packages/cli/src/procedures/analyzeCookieUrls.ts b/packages/cli/src/procedures/analyzeCookieUrls.ts index 17a951309..93a8241f4 100644 --- a/packages/cli/src/procedures/analyzeCookieUrls.ts +++ b/packages/cli/src/procedures/analyzeCookieUrls.ts @@ -29,7 +29,8 @@ export const analyzeCookiesUrls = async ( urls: string[], isHeadless: boolean, delayTime: number, - cookieDictionary: CookieDatabase + cookieDictionary: CookieDatabase, + shouldSkipAcceptBanner: boolean ) => { const browser = new BrowserManagement( { @@ -43,7 +44,10 @@ export const analyzeCookiesUrls = async ( ); await browser.initializeBrowser(true); - const analysisCookieData = await browser.analyzeCookieUrls(urls); + const analysisCookieData = await browser.analyzeCookieUrls( + urls, + shouldSkipAcceptBanner + ); const res = analysisCookieData.map(({ pageUrl, cookieData }) => { Object.entries(cookieData).forEach(([, frameData]) => { diff --git a/packages/cli/src/procedures/analyzeCookieUrlsInBatches.ts b/packages/cli/src/procedures/analyzeCookieUrlsInBatches.ts index db27f6f2c..93df0200c 100644 --- a/packages/cli/src/procedures/analyzeCookieUrlsInBatches.ts +++ b/packages/cli/src/procedures/analyzeCookieUrlsInBatches.ts @@ -36,7 +36,8 @@ export const analyzeCookiesUrlsInBatches = async ( id: string, { text, indent }: { text: string; indent: number } ) => void; - } + }, + shouldSkipAcceptBanner = false ) => { let report: { pageUrl: string; @@ -66,7 +67,8 @@ export const analyzeCookiesUrlsInBatches = async ( urlsWindow, isHeadless, delayTime, - cookieDictionary + cookieDictionary, + shouldSkipAcceptBanner ); report = [...report, ...cookieAnalysis]; diff --git a/packages/cli/src/utils/browserManagement/index.ts b/packages/cli/src/utils/browserManagement/index.ts index 134b2a44f..c64b87468 100644 --- a/packages/cli/src/utils/browserManagement/index.ts +++ b/packages/cli/src/utils/browserManagement/index.ts @@ -75,6 +75,52 @@ export class BrowserManagement { this.debugLog('browser intialized'); } + async clickOnAcceptBanner(url: string) { + const page = this.pageMap.get(url); + + if (!page) { + throw new Error('no page with the provided id was found'); + } + + await page.evaluate(() => { + const bannerNodes: Element[] = Array.from( + (document.querySelector('body')?.childNodes || []) as Element[] + ) + .filter((node: Element) => node && node?.tagName === 'DIV') + .filter((node) => { + if (!node || !node?.textContent) { + return false; + } + const regex = + /\b(consent|policy|cookie policy|privacy policy|personalize|preferences)\b/; + + return regex.test(node.textContent.toLowerCase()); + }); + + if (bannerNodes.length > 0) { + this.debugLog(`found GDPR banner in the page.`); + } + + const buttonToClick: HTMLButtonElement[] = bannerNodes + .map((node: Element) => { + const buttonNodes = Array.from(node.getElementsByTagName('button')); + const isButtonForAccept = buttonNodes.filter( + (cnode) => + cnode.textContent && + (cnode.textContent.toLowerCase().includes('accept') || + cnode.textContent.toLowerCase().includes('allow') || + cnode.textContent.toLowerCase().includes('agree')) + ); + + return isButtonForAccept[0]; + }) + .filter((button) => button); + buttonToClick[0]?.click(); + }); + + await delay(this.pageWaitTime / 2); + } + async openPage(): Promise { if (!this.browser) { throw new Error('Browser not intialized'); @@ -90,33 +136,51 @@ export class BrowserManagement { height: 790, deviceScaleFactor: 1, }); + this.debugLog('Page opened'); + return sitePage; } - async navigateAndScroll(url: string) { + async navigateToPage(url: string) { const page = this.pageMap.get(url); + if (!page) { throw new Error('no page with the provided id was found'); } + this.debugLog(`starting navigation to url ${url}`); + try { await page.goto(url, { timeout: 10000 }); + this.debugLog(`done with navigation to url:${url}`); } catch (error) { this.debugLog( `navigation did not finish in 10 seconds moving on to scrolling` ); //ignore } + } - await delay(this.pageWaitTime / 2); + async pageScroll(url: string) { + const page = this.pageMap.get(url); - await page.evaluate(() => { - window.scrollBy(0, 10000); - }); + if (!page) { + throw new Error('no page with the provided id was found'); + } + + try { + await page.evaluate(() => { + window.scrollBy(0, 10000); + }); + } catch (error) { + this.debugLog(`scrolling the page to the end.`); + //ignore + } await delay(this.pageWaitTime / 2); - this.debugLog(`done navigating and scrolling to url:${url}`); + + this.debugLog(`scrolling on url:${url}`); } async attachNetworkListenersToPage(pageId: string) { @@ -254,7 +318,7 @@ export class BrowserManagement { return frameIdMapFromTree; } - async analyzeCookieUrls(urls: string[]) { + async analyzeCookieUrls(urls: string[], shouldSkipAcceptBanner: boolean) { for (const url of urls) { const sitePage = await this.openPage(); this.pageMap.set(url, sitePage); @@ -264,7 +328,11 @@ export class BrowserManagement { // start navigation in parallel await Promise.all( urls.map(async (url) => { - await this.navigateAndScroll(url); + await this.navigateToPage(url); + if (shouldSkipAcceptBanner) { + await this.clickOnAcceptBanner(url); + } + await this.pageScroll(url); }) ); @@ -297,7 +365,7 @@ export class BrowserManagement { requestMap, frameIdUrlMap, mainFrameId, - url + page.url() ); const networkCookieKeySet = new Set(); diff --git a/packages/cli/src/utils/browserManagement/parseNetworkDataToCookieData.ts b/packages/cli/src/utils/browserManagement/parseNetworkDataToCookieData.ts index a548826d4..a8f41e15b 100644 --- a/packages/cli/src/utils/browserManagement/parseNetworkDataToCookieData.ts +++ b/packages/cli/src/utils/browserManagement/parseNetworkDataToCookieData.ts @@ -85,11 +85,15 @@ export const parseNetworkDataToCookieData = ( data.responses?.forEach((response: ResponseData) => { response.cookies.forEach((cookie) => { // domain update required. Domain based on the server url - const parsedDomain = + let parsedDomain = cookie.parsedCookie.domain === '' ? getDomain(response.serverUrl) : cookie.parsedCookie.domain; + if (parsedDomain && parsedDomain[0] !== '.') { + parsedDomain = '.' + parsedDomain; + } + const key = cookie.parsedCookie.name + ':' + @@ -119,11 +123,15 @@ export const parseNetworkDataToCookieData = ( data.requests?.forEach((request: RequestData) => { request.cookies.forEach((cookie) => { // domain update required. Domain based on the server url - const parsedDomain = + let parsedDomain = cookie.parsedCookie.domain === '' ? getDomain(request.serverUrl) : cookie.parsedCookie.domain; + if (parsedDomain && parsedDomain[0] !== '.') { + parsedDomain = '.' + parsedDomain; + } + const key = cookie.parsedCookie.name + ':' + @@ -148,7 +156,7 @@ export const parseNetworkDataToCookieData = ( }); frameIdCookiesMap.set(frameId, { - frameUrl: frameIdUrlMap.get(frameId) || pageUrl, + frameUrl: frameIdUrlMap.get(frameId) || new URL(pageUrl).origin, frameCookies: Object.fromEntries(_frameCookies), }); } @@ -166,6 +174,7 @@ export const parseNetworkDataToCookieData = ( if (!data.frameUrl.includes('http')) { continue; } + const _url = new URL(data.frameUrl); const newFrameCookies = { diff --git a/packages/cli/src/utils/generateCSVfiles.ts b/packages/cli/src/utils/generateCSVfiles.ts index c31746868..b29534698 100644 --- a/packages/cli/src/utils/generateCSVfiles.ts +++ b/packages/cli/src/utils/generateCSVfiles.ts @@ -18,7 +18,7 @@ */ import { CompleteJson, - generateAffectedCookiesCSV, + generateCookiesWithIssuesCSV, generateAllCookiesCSV, generateSummaryDataCSV, generateTechnologyCSV, @@ -30,13 +30,13 @@ const generateCSVFiles = (data: CompleteJson) => { if (data.technologyData.length > 0) { technologyDataCSV = generateTechnologyCSV(data); } - const affectedCookiesDataCSV = generateAffectedCookiesCSV(data); + const cookiesWithIssuesDataCSV = generateCookiesWithIssuesCSV(data); const summaryDataCSV = generateSummaryDataCSV(data); return { allCookiesCSV, technologyDataCSV, - affectedCookiesDataCSV, + cookiesWithIssuesDataCSV, summaryDataCSV, }; }; diff --git a/packages/cli/src/utils/saveCSVReports.ts b/packages/cli/src/utils/saveCSVReports.ts index 479d53954..d8a1d5c55 100644 --- a/packages/cli/src/utils/saveCSVReports.ts +++ b/packages/cli/src/utils/saveCSVReports.ts @@ -47,7 +47,7 @@ const saveCSVReports = async (outDir: string, result: CompleteJson[]) => { const { allCookiesCSV, technologyDataCSV, - affectedCookiesDataCSV, + cookiesWithIssuesDataCSV, summaryDataCSV, } = generateCSVFiles(siteReport); @@ -64,10 +64,10 @@ const saveCSVReports = async (outDir: string, result: CompleteJson[]) => { ); } - await ensureFile(path.join(fileDir, 'affected-cookies.csv')); + await ensureFile(path.join(fileDir, 'cookie-issues.csv')); await writeFile( - path.join(fileDir, 'affected-cookies.csv'), - affectedCookiesDataCSV + path.join(fileDir, 'cookies-issues.csv'), + cookiesWithIssuesDataCSV ); await ensureFile(path.join(fileDir, 'report.csv')); @@ -79,7 +79,7 @@ const saveCSVReports = async (outDir: string, result: CompleteJson[]) => { const { allCookiesCSV, technologyDataCSV, - affectedCookiesDataCSV, + cookiesWithIssuesDataCSV, summaryDataCSV, } = generateCSVFiles(result[0]); await ensureFile(path.join(outDir, 'cookies.csv')); @@ -90,10 +90,10 @@ const saveCSVReports = async (outDir: string, result: CompleteJson[]) => { await writeFile(path.join(outDir, 'technologies.csv'), technologyDataCSV); } - await ensureFile(path.join(outDir, 'affected-cookies.csv')); + await ensureFile(path.join(outDir, 'cookie-issues.csv')); await writeFile( - path.join(outDir, 'affected-cookies.csv'), - affectedCookiesDataCSV + path.join(outDir, 'cookie-issues.csv'), + cookiesWithIssuesDataCSV ); await ensureFile(path.join(outDir, 'report.csv')); diff --git a/packages/cli/src/utils/tests/validateArgs.ts b/packages/cli/src/utils/tests/validateArgs.ts index 2cf3df480..bcfb0c5f2 100644 --- a/packages/cli/src/utils/tests/validateArgs.ts +++ b/packages/cli/src/utils/tests/validateArgs.ts @@ -44,7 +44,7 @@ describe('validateArgs', () => { return; }); - validateArgs('https://example.com', '', '', '', '', ''); + validateArgs('https://example.com', '', '', '', '', '', 9000); expect(mockExit).toHaveBeenCalledTimes(0); }); @@ -59,7 +59,7 @@ describe('validateArgs', () => { jest.spyOn(fse, 'mkdir').mockImplementation(() => { return; }); - validateArgs('', '', '', '', '', ''); + validateArgs('', '', '', '', '', '', 9000); expect(mockExit).toHaveBeenCalled(); }); @@ -80,7 +80,8 @@ describe('validateArgs', () => { '', '', '', - '' + '', + 9000 ); expect(mockExit).toHaveBeenCalled(); @@ -92,7 +93,7 @@ describe('validateArgs', () => { return false; }); - validateArgs('', '', '', './path/list.xml', '', ''); + validateArgs('', '', '', './path/list.xml', '', '', 9000); expect(mockExit).toHaveBeenCalled(); }); @@ -103,7 +104,7 @@ describe('validateArgs', () => { return true; }); - validateArgs('', '', '', './path/list.xml', 'a', ''); + validateArgs('', '', '', './path/list.xml', 'a', '', 9000); expect(mockExit).toHaveBeenCalled(); }); diff --git a/packages/cli/src/utils/validateArgs.ts b/packages/cli/src/utils/validateArgs.ts index 1b75e1c2e..f6753d7b2 100644 --- a/packages/cli/src/utils/validateArgs.ts +++ b/packages/cli/src/utils/validateArgs.ts @@ -26,6 +26,7 @@ import path from 'path'; * @param {string} sitemapPath File system path to a sitemap xml file. * @param {string} numberOfUrls Url limit argument. * @param {string} outDir File system path to the output directory. + * @param port */ // eslint-disable-next-line complexity const validateArgs = async ( @@ -34,8 +35,14 @@ const validateArgs = async ( csvPath: string, sitemapPath: string, numberOfUrls: string, - outDir: string + outDir: string, + port: number ) => { + if (isNaN(port) || (!isNaN(port) && (port < 0 || port > 65536))) { + console.log(`Invalid port argument. Please porvide a port >=0 and <=65536`); + process.exit(1); + } + const numArgs: number = [ Boolean(url), Boolean(sitemapUrl), @@ -94,9 +101,7 @@ const validateArgs = async ( if (outDir) { const outDirExists = await exists(path.resolve(outDir)); if (!outDirExists) { - console.log( - `Provided dir "${path.resolve(outDir)}" does not exist. Creating now!!` - ); + console.log(`"${path.resolve(outDir)}" does not exist, creating now.`); await mkdir(path.resolve(outDir)); } } diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 9bd045188..9df306aff 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -9,5 +9,5 @@ "esModuleInterop": true, "moduleResolution": "node" }, - "references": [{ "path": "../common" }] + "references": [{ "path": "../common" }, { "path": "../i18n" }] } diff --git a/packages/common/package.json b/packages/common/package.json index aad0a1bc0..f363e81da 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@ps-analysis-tool/common", - "version": "0.6.0", + "version": "0.7.0", "description": "A package for common utilities that are being used in multiple packages", "main": "./dist/index.js", "types": "./dist-types/index.d.ts", @@ -28,6 +28,6 @@ "tldts": "^6.0.14" }, "devDependencies": { - "devtools-protocol": "^0.0.1236148" + "devtools-protocol": "^0.0.1282316" } } diff --git a/packages/common/src/constants/index.ts b/packages/common/src/constants/index.ts index ff859b711..92c2b2764 100644 --- a/packages/common/src/constants/index.ts +++ b/packages/common/src/constants/index.ts @@ -14,5 +14,3 @@ * limitations under the License. */ export const UNKNOWN_FRAME_KEY = 'Unknown Frames'; -export const ORPHANED_COOKIE_KEY = 'Orphaned Cookies'; -export const UNMAPPED_COOKIE_KEY = 'Unmapped Cookies'; diff --git a/packages/common/src/cookies.types.ts b/packages/common/src/cookies.types.ts index 3ad966c21..13ed47885 100644 --- a/packages/common/src/cookies.types.ts +++ b/packages/common/src/cookies.types.ts @@ -40,6 +40,10 @@ export type CookiesCount = { total: number; [key: string]: number; }; + exemptedCookies: { + total: number; + [key: string]: number; + }; }; export type CookieAnalytics = { @@ -119,11 +123,11 @@ export type CookieData = { inboundBlock: BLOCK_STATUS; outboundBlock: BLOCK_STATUS; }; + exemptionReason?: Protocol.Network.CookieExemptionReason; }; export type CookieTableData = CookieData & { frameUrls?: string | string[]; - highlighted?: boolean; isDomainInAllowList?: boolean; }; @@ -162,11 +166,13 @@ export interface Legend { count: number; color: string; countClassName: string; + onClick?: (title: string) => void; } export interface CookieStatsComponents { legend: Legend[]; blockedCookiesLegend: Legend[]; + exemptedCookiesLegend: Legend[]; firstParty: { count: number; color: string; @@ -179,6 +185,10 @@ export interface CookieStatsComponents { count: number; color: string; }[]; + exempted: { + count: number; + color: string; + }[]; } export interface FramesWithCookies { @@ -209,6 +219,10 @@ export type CookieJsonDataType = { requestUrls?: { [id: string]: string }; frameUrls?: { [id: string]: string }; isBlocked: boolean; + blockingStatus?: { + inboundBlock: BLOCK_STATUS; + outboundBlock: BLOCK_STATUS; + }; blockedReasons?: BlockedReason[]; }; @@ -230,3 +244,18 @@ export type CompleteJson = { }; technologyData: TechnologyData[]; }; + +export interface DataMapping { + title: string; + count: number; + data: { + count: number; + color: string; + }[]; + onClick?: () => void; +} + +export type FrameStateCreator = { + dataMapping: DataMapping[]; + legend: Legend[]; +}; diff --git a/packages/common/src/data/cookieExemptionReason/exemptionReasons.ts b/packages/common/src/data/cookieExemptionReason/exemptionReasons.ts new file mode 100644 index 000000000..a53e27ad9 --- /dev/null +++ b/packages/common/src/data/cookieExemptionReason/exemptionReasons.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +//For source see https://source.chromium.org/chromium/chromium/src/+/main:third_party/devtools-frontend/src/front_end/core/sdk/NetworkRequest.ts +const CookieExemptionReason = { + UserSetting: 'This cookie is allowed by user preference.', + TPCDMetadata: + 'This cookie is allowed by a third-party cookie deprecation trial grace period.', + TPCDDeprecationTrial: + 'This cookie is allowed by third-party cookie phaseout deprecation trial.', + TPCDHeuristics: + 'This cookie is allowed by third-party cookie phaseout heuristics.', + EnterprisePolicy: 'This cookie is allowed by Chrome Enterprise policy.', + StorageAccessAPI: 'This cookie is allowed by the Storage Access API.', + TopLevelStorageAccessAPI: + 'This cookie is allowed by the top-level Storage Access API.', + CorsOptIn: 'This cookie is allowed by CORS opt-in', +}; + +export default CookieExemptionReason; diff --git a/packages/common/src/data/cookieExemptionReason/index.ts b/packages/common/src/data/cookieExemptionReason/index.ts new file mode 100644 index 000000000..3628d84e8 --- /dev/null +++ b/packages/common/src/data/cookieExemptionReason/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { default as cookieExemptionReason } from './exemptionReasons'; diff --git a/packages/common/src/data/index.ts b/packages/common/src/data/index.ts new file mode 100644 index 000000000..62c0d39c7 --- /dev/null +++ b/packages/common/src/data/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { default as cookieIssueDetails } from './cookieExclusionAndWarningReasons'; +export { cookieExemptionReason } from './cookieExemptionReason'; diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 1ec04a46b..3bae5d459 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -26,7 +26,7 @@ export { default as calculateEffectiveExpiryDate } from './utils/calculateEffect export { default as sanitizeCsvRecord } from './utils/sanitizeCsvRecord'; export { parseUrl } from './utils/parseUrl'; export { default as fetchLocalData } from './utils/fetchLocalData'; -export { default as cookieIssueDetails } from './data/cookieExclusionAndWarningReasons'; +export * from './data'; export { default as parseResponseReceivedExtraInfo } from './utils/parseResponseReceivedExtraInfo'; export { default as parseRequestWillBeSentExtraInfo } from './utils/parseRequestWillBeSentExtraInfo'; export { default as getDomainFromUrl } from './utils/getDomainFromUrl'; @@ -35,6 +35,7 @@ export { default as noop } from './utils/noop'; export { default as getDevToolWorker } from './worker/devToolWorker'; export { default as executeTaskInDevToolWorker } from './worker/executeTaskInDevToolWorker'; export { default as getValueByKey } from './utils/getValueByKey'; +export * from './utils/contextSelector'; export { default as addUTMParams } from './utils/addUTMParams'; export * from './worker/enums'; export * from './utils/generateReports'; diff --git a/packages/common/src/utils/contextSelector/createContext.ts b/packages/common/src/utils/contextSelector/createContext.ts new file mode 100644 index 000000000..512a96cd7 --- /dev/null +++ b/packages/common/src/utils/contextSelector/createContext.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { createContext as createContextOrig } from 'use-context-selector'; + +const createContext = (defaultValue: T) => { + return createContextOrig(defaultValue); +}; + +export default createContext; diff --git a/packages/common/src/utils/contextSelector/index.ts b/packages/common/src/utils/contextSelector/index.ts new file mode 100644 index 000000000..7becbbca9 --- /dev/null +++ b/packages/common/src/utils/contextSelector/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { default as useContextSelector } from './useContextSelector'; +export { default as createContext } from './createContext'; diff --git a/packages/extension/src/utils/shallowEqual.ts b/packages/common/src/utils/contextSelector/shallowEqual.ts similarity index 83% rename from packages/extension/src/utils/shallowEqual.ts rename to packages/common/src/utils/contextSelector/shallowEqual.ts index 49ae656e1..b5f270073 100644 --- a/packages/extension/src/utils/shallowEqual.ts +++ b/packages/common/src/utils/contextSelector/shallowEqual.ts @@ -24,6 +24,14 @@ export const shallowEqual = (a: unknown, b: unknown): boolean => { } if (Array.isArray(a) && Array.isArray(b)) { + if ( + typeof a[0] === 'object' && + typeof b[0] === 'object' && + a.length === b.length + ) { + return a.every((item, index) => shallowEqualObjects(item, b[index])); + } + return shallowEqualArrays(a, b); } diff --git a/packages/common/src/utils/contextSelector/tests/shallowEqual.ts b/packages/common/src/utils/contextSelector/tests/shallowEqual.ts new file mode 100644 index 000000000..1ebe6e611 --- /dev/null +++ b/packages/common/src/utils/contextSelector/tests/shallowEqual.ts @@ -0,0 +1,86 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ + +/** + * Internal dependencies + */ +import { shallowEqual } from '../shallowEqual'; + +describe('shallowEqual', () => { + it('should return true if the objects are equal', () => { + const a = { a: 1, b: 2 }; + const b = { a: 1, b: 2 }; + expect(shallowEqual(a, b)).toBe(true); + }); + + it('should return false if the objects are not equal', () => { + const a = { a: 1, b: 2 }; + const b = { a: 1, b: 3 }; + expect(shallowEqual(a, b)).toBe(false); + }); + + it('should return true if the arrays are equal', () => { + const a = [1, 2, 3]; + const b = [1, 2, 3]; + expect(shallowEqual(a, b)).toBe(true); + }); + + it('should return false if the arrays are not equal', () => { + const a = [1, 2, 3]; + const b = [1, 2, 4]; + expect(shallowEqual(a, b)).toBe(false); + }); + + it('should return true if the arrays of objects are equal', () => { + const a = [ + { a: 1, b: 2 }, + { a: 3, b: 4 }, + ]; + const b = [ + { a: 1, b: 2 }, + { a: 3, b: 4 }, + ]; + expect(shallowEqual(a, b)).toBe(true); + }); + + it('should return false if the arrays of objects are not equal', () => { + const a = [ + { a: 1, b: 2 }, + { a: 3, b: 4 }, + ]; + const b = [ + { a: 1, b: 2 }, + { a: 3, b: 5 }, + ]; + expect(shallowEqual(a, b)).toBe(false); + }); + + it('should return false if the arrays of objects are not equal', () => { + const a = [ + { a: 1, b: 2 }, + { a: 3, b: 4 }, + ]; + const b = [ + { a: 1, b: 2 }, + { a: 3, b: 5 }, + ]; + expect(shallowEqual(a, b)).toBe(false); + }); +}); diff --git a/packages/extension/src/utils/useContextSelector.ts b/packages/common/src/utils/contextSelector/useContextSelector.ts similarity index 100% rename from packages/extension/src/utils/useContextSelector.ts rename to packages/common/src/utils/contextSelector/useContextSelector.ts diff --git a/packages/common/src/utils/fetchLocalData.ts b/packages/common/src/utils/fetchLocalData.ts index bf6742271..92cc955fc 100644 --- a/packages/common/src/utils/fetchLocalData.ts +++ b/packages/common/src/utils/fetchLocalData.ts @@ -26,10 +26,7 @@ const fetchLocalData = async (path: string) => { return await response.json(); } catch (error) { // eslint-disable-next-line no-console - console.warn( - `Failed to fetch local data from path: ${path}. Error:`, - error - ); + console.log(`Failed to fetch local data from path: ${path}. Error:`, error); return []; } diff --git a/packages/common/src/utils/filterCookiesByFrame.ts b/packages/common/src/utils/filterCookiesByFrame.ts index b36a33b4f..458b15731 100644 --- a/packages/common/src/utils/filterCookiesByFrame.ts +++ b/packages/common/src/utils/filterCookiesByFrame.ts @@ -16,22 +16,17 @@ /** * Internal dependencies. */ -import { CookieTableData } from '../cookies.types'; - -interface Cookies { - [key: string]: CookieTableData; -} - +import { TabCookies } from '../cookies.types'; interface TabFrames { [key: string]: { frameIds: number[] }; } const filterCookiesByFrame = ( - cookies: Cookies | null, + cookies: TabCookies | null, tabFrames: TabFrames | null, frameUrl: string | null ) => { - const frameFilteredCookies: { [key: string]: CookieTableData } = {}; + const frameFilteredCookies: TabCookies = {}; if (!cookies || !frameUrl || !tabFrames || !tabFrames[frameUrl]) { return Object.values(frameFilteredCookies); } diff --git a/packages/common/src/utils/generateReports/generateAllCookiesCSV.ts b/packages/common/src/utils/generateReports/generateAllCookiesCSV.ts index 70161e40d..59bbae22c 100644 --- a/packages/common/src/utils/generateReports/generateAllCookiesCSV.ts +++ b/packages/common/src/utils/generateReports/generateAllCookiesCSV.ts @@ -21,7 +21,10 @@ import sanitizeCsvRecord from '../sanitizeCsvRecord'; /** * Internal dependencies */ -import type { CompleteJson, CookieJsonDataType } from '../../cookies.types'; +import { + type CompleteJson, + type CookieJsonDataType, +} from '../../cookies.types'; import calculateEffectiveExpiryDate from '../calculateEffectiveExpiryDate'; export const COOKIES_DATA_HEADER = [ @@ -37,7 +40,7 @@ export const COOKIES_DATA_HEADER = [ 'Value', 'Path', 'Expires', - 'Cookie Affected', + 'Issues', 'GDPRPortal', ]; diff --git a/packages/common/src/utils/generateReports/generateAffectedCookiesCSV.ts b/packages/common/src/utils/generateReports/generateCookiesWithIssuesCSV.ts similarity index 79% rename from packages/common/src/utils/generateReports/generateAffectedCookiesCSV.ts rename to packages/common/src/utils/generateReports/generateCookiesWithIssuesCSV.ts index 15e2cbff3..a1ec91685 100644 --- a/packages/common/src/utils/generateReports/generateAffectedCookiesCSV.ts +++ b/packages/common/src/utils/generateReports/generateCookiesWithIssuesCSV.ts @@ -17,11 +17,11 @@ /** * Internal dependencies */ -import { CompleteJson, CookieJsonDataType } from '../../cookies.types'; +import type { CompleteJson, CookieJsonDataType } from '../../cookies.types'; import calculateEffectiveExpiryDate from '../calculateEffectiveExpiryDate'; import sanitizeCsvRecord from '../sanitizeCsvRecord'; -export const AFFECTED_COOKIES_DATA_HEADERS = [ +export const COOKIES_WITH_ISSUES_DATA_HEADERS = [ 'Name', 'Scope', 'Domain', @@ -37,23 +37,25 @@ export const AFFECTED_COOKIES_DATA_HEADERS = [ 'GDPRPortal', ]; -const generateAffectedCookiesCSV = (siteAnalysisData: CompleteJson): string => { +const generateCookiesWithIssuesCSV = ( + siteAnalysisData: CompleteJson +): string => { const frameCookieDataMap = siteAnalysisData.cookieData; - const affectedCookieMap: Map = new Map(); + const CookieWithIssueMap: Map = new Map(); // More than one frame can use one cookie, need to make a map for getting unique entries. Object.entries(frameCookieDataMap).forEach(([, { frameCookies }]) => { Object.entries(frameCookies).forEach(([cookieKey, cookieData]) => { if (cookieData.isBlocked) { - affectedCookieMap.set(cookieKey, cookieData); + CookieWithIssueMap.set(cookieKey, cookieData); } }); }); let cookieRecords = ''; - for (const cookie of affectedCookieMap.values()) { + for (const cookie of CookieWithIssueMap.values()) { //This should be in the same order as cookieDataHeader const recordsArray = [ cookie.parsedCookie.name, @@ -74,7 +76,7 @@ const generateAffectedCookiesCSV = (siteAnalysisData: CompleteJson): string => { cookieRecords += recordsArray.join(',') + '\r\n'; } - return AFFECTED_COOKIES_DATA_HEADERS.join(',') + '\r\n' + cookieRecords; + return COOKIES_WITH_ISSUES_DATA_HEADERS.join(',') + '\r\n' + cookieRecords; }; -export default generateAffectedCookiesCSV; +export default generateCookiesWithIssuesCSV; diff --git a/packages/common/src/utils/generateReports/generateSummaryDataCSV.ts b/packages/common/src/utils/generateReports/generateSummaryDataCSV.ts index c2c1cc501..218bbba32 100644 --- a/packages/common/src/utils/generateReports/generateSummaryDataCSV.ts +++ b/packages/common/src/utils/generateReports/generateSummaryDataCSV.ts @@ -37,11 +37,11 @@ const generateSummaryDataCSV = (siteAnalysisData: CompleteJson): string => { let functionalCookies = 0; let marketingCookies = 0; let uncategorisedCookies = 0; - let affectedCookies = 0; - let affectedAnalyticsCookies = 0; - let affectedFunctionalCookies = 0; - let affectedMarketingCookies = 0; - let affectedUncategorisedCookies = 0; + let cookiesWithIssues = 0; + let analyticsCookiesWithIssues = 0; + let functionalCookiesWithIssues = 0; + let marketingCookiesWithIssues = 0; + let uncategorisedCookiesWithIssues = 0; for (const cookie of cookieMap.values()) { if (cookie.isFirstParty) { @@ -51,32 +51,32 @@ const generateSummaryDataCSV = (siteAnalysisData: CompleteJson): string => { } if (cookie.isBlocked) { - affectedCookies += 1; + cookiesWithIssues += 1; } switch (cookie.analytics.category) { case 'Analytics': analyticsCookies += 1; if (cookie.isBlocked) { - affectedAnalyticsCookies += 1; + analyticsCookiesWithIssues += 1; } break; case 'Functional': functionalCookies += 1; if (cookie.isBlocked) { - affectedFunctionalCookies += 1; + functionalCookiesWithIssues += 1; } break; case 'Marketing': marketingCookies += 1; if (cookie.isBlocked) { - affectedMarketingCookies += 1; + marketingCookiesWithIssues += 1; } break; case 'Uncategorized': uncategorisedCookies += 1; if (cookie.isBlocked) { - affectedUncategorisedCookies += 1; + uncategorisedCookiesWithIssues += 1; } break; default: @@ -92,11 +92,11 @@ const generateSummaryDataCSV = (siteAnalysisData: CompleteJson): string => { 'Functional Cookies': functionalCookies, 'Marketing Cookies': marketingCookies, 'Uncategorized Cookies': uncategorisedCookies, - 'Affected Cookies': affectedCookies, - 'Affected Analytics Cookies': affectedAnalyticsCookies, - 'Affected Functional Cookies': affectedFunctionalCookies, - 'Affected Marketing Cookies': affectedMarketingCookies, - 'Affected Uncategorized Cookies': affectedUncategorisedCookies, + 'Cookies With Issues': cookiesWithIssues, + 'Analytics Cookies With Issues': analyticsCookiesWithIssues, + 'Functional Cookies With Issues': functionalCookiesWithIssues, + 'Marketing Cookies With Issues': marketingCookiesWithIssues, + 'Uncategorized Cookies With Issues': uncategorisedCookiesWithIssues, }; const CSVString = Object.entries(summary).reduce( diff --git a/packages/common/src/utils/generateReports/index.ts b/packages/common/src/utils/generateReports/index.ts index 12c0d45a3..75a64d39b 100644 --- a/packages/common/src/utils/generateReports/index.ts +++ b/packages/common/src/utils/generateReports/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -export { default as generateAffectedCookiesCSV } from './generateAffectedCookiesCSV'; +export { default as generateCookiesWithIssuesCSV } from './generateCookiesWithIssuesCSV'; export { default as generateAllCookiesCSV } from './generateAllCookiesCSV'; export { default as generateSummaryDataCSV } from './generateSummaryDataCSV'; export { default as generateTechnologyCSV } from './generateTechnologyCSV'; diff --git a/packages/common/src/utils/generateReports/tests/generateAffectedCookiesCSV.ts b/packages/common/src/utils/generateReports/tests/generateAffectedCookiesCSV.ts index b44cb711d..74d8fc438 100644 --- a/packages/common/src/utils/generateReports/tests/generateAffectedCookiesCSV.ts +++ b/packages/common/src/utils/generateReports/tests/generateAffectedCookiesCSV.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import generateAffectedCookiesCSV from '../generateAffectedCookiesCSV'; +import generateCookiesWithIssuesCSV from '../generateCookiesWithIssuesCSV'; import { mockData1 } from './data.mock'; -describe('generateAffectedCookiesCSV', () => { - it('should create CSV string for affected cookies', () => { - const CSVString = generateAffectedCookiesCSV(mockData1); +describe('generateCookiesWithIssuesCSV', () => { + it('should create CSV string for cookies with issues', () => { + const CSVString = generateCookiesWithIssuesCSV(mockData1); expect(CSVString.split('\r\n').filter((str) => str).length).toBe(4); }); diff --git a/packages/common/src/utils/parseRequestWillBeSentExtraInfo.ts b/packages/common/src/utils/parseRequestWillBeSentExtraInfo.ts index f434c6213..a599b60be 100644 --- a/packages/common/src/utils/parseRequestWillBeSentExtraInfo.ts +++ b/packages/common/src/utils/parseRequestWillBeSentExtraInfo.ts @@ -31,21 +31,23 @@ import isFirstParty from './isFirstParty'; /** * Parses Network.requestWillBeSentExtraInfo to get extra information about a cookie. - * @param {object} request Request to be parsed to get extra information about a cookie. + * @param {object} associatedCookies Cookies associated with the request being parsed. * @param {object} cookieDB Cookie database to find analytics from. * @param {object} requestMap An object for requestId to url. * @param {string} tabUrl - The top-level URL (URL in the tab's address bar). + * @param {string} requestId - The requestId of the request being processed * @returns {object} parsed cookies. */ export default function parseRequestWillBeSentExtraInfo( - request: Protocol.Network.RequestWillBeSentExtraInfoEvent, + associatedCookies: Protocol.Network.RequestWillBeSentExtraInfoEvent['associatedCookies'], cookieDB: CookieDatabase, requestMap: { [requestId: string]: string }, - tabUrl: string + tabUrl: string, + requestId: string ) { const cookies: CookieData[] = []; - request.associatedCookies.forEach(({ blockedReasons, cookie }) => { + associatedCookies.forEach(({ blockedReasons, cookie, exemptionReason }) => { const effectiveExpirationDate = calculateEffectiveExpiryDate( cookie.expires ); @@ -53,8 +55,8 @@ export default function parseRequestWillBeSentExtraInfo( let domain, url = ''; - if (requestMap && requestMap[request?.requestId]) { - url = requestMap[request?.requestId] ?? ''; + if (requestMap && requestMap[requestId]) { + url = requestMap[requestId] ?? ''; } if (cookie?.domain) { @@ -75,7 +77,7 @@ export default function parseRequestWillBeSentExtraInfo( requestEvents: [ { type: REQUEST_EVENT.CDP_REQUEST_WILL_BE_SENT_EXTRA_INFO, - requestId: request.requestId, + requestId, url: url, blocked: blockedReasons.length !== 0, timeStamp: Date.now(), @@ -89,6 +91,7 @@ export default function parseRequestWillBeSentExtraInfo( headerType: 'request' as CookieData['headerType'], isFirstParty: isFirstParty(domain, tabUrl), frameIdList: [], + exemptionReason: exemptionReason ? exemptionReason : undefined, }; cookies.push(singleCookie); diff --git a/packages/common/src/utils/parseResponseReceivedExtraInfo.ts b/packages/common/src/utils/parseResponseReceivedExtraInfo.ts index c5a6005f1..a43d6d706 100644 --- a/packages/common/src/utils/parseResponseReceivedExtraInfo.ts +++ b/packages/common/src/utils/parseResponseReceivedExtraInfo.ts @@ -33,26 +33,33 @@ import isFirstParty from './isFirstParty'; /** * Parse Network.responseReceivedExtraInfo for extra information about a cookie. - * @param {object} response Response to be parsed to get extra information about a cookie. + * @param {object} headers Headers of resonse to be parsed to get extra information about a cookie. + * @param {object} blockedCookies Blocked Cookies associated with the response being parsed. + * @param {object} exemptedCookies Blocked Cookies associated with the response being parsed. + * @param {string|undefined} cookiePartitionKey Partittion key for the response. * @param {object} requestMap An object for requestId to url. * @param {string} tabUrl - The top-level URL (URL in the tab's address bar). * @param {object} cookieDB Cookie database to find analytics from. + * @param {string} requestId - The requestId of the request being processed * @returns {object} parsed cookies. */ export default function parseResponseReceivedExtraInfo( - response: Protocol.Network.ResponseReceivedExtraInfoEvent, + headers: Protocol.Network.ResponseReceivedExtraInfoEvent['headers'], + blockedCookies: Protocol.Network.ResponseReceivedExtraInfoEvent['blockedCookies'], + exemptedCookies: Protocol.Network.ResponseReceivedExtraInfoEvent['exemptedCookies'], + cookiePartitionKey: Protocol.Network.ResponseReceivedExtraInfoEvent['cookiePartitionKey'], requestMap: { [requestId: string]: string }, tabUrl: string, - cookieDB: CookieDatabase + cookieDB: CookieDatabase, + requestId: string ) { const cookies: CookieData[] = []; - const responseToParse = - response.headers['set-cookie'] ?? response.headers['Set-Cookie']; + const responseToParse = headers['set-cookie'] ?? headers['Set-Cookie']; responseToParse?.split('\n').forEach((headerLine: string) => { let parsedCookie: CookieData['parsedCookie'] = parse(headerLine); - const blockedCookie = response.blockedCookies.find((c) => { + const blockedCookie = blockedCookies.find((c) => { if (c.cookie) { return c.cookie?.name === parsedCookie.name; } else { @@ -61,6 +68,13 @@ export default function parseResponseReceivedExtraInfo( } }); + const exemptedCookie = exemptedCookies?.find((c) => { + if (c.cookie) { + return c.cookie?.name === parsedCookie.name; + } + return false; + }); + const effectiveExpirationDate = calculateEffectiveExpiryDate( parsedCookie.expires ); @@ -68,15 +82,15 @@ export default function parseResponseReceivedExtraInfo( if (headerLine.toLowerCase().includes('partitioned')) { parsedCookie = { ...parsedCookie, - partitionKey: response?.cookiePartitionKey, + partitionKey: cookiePartitionKey, }; } let domain, url = ''; - if (requestMap && requestMap[response?.requestId]) { - url = requestMap[response?.requestId] ?? ''; + if (requestMap && requestMap[requestId]) { + url = requestMap[requestId] ?? ''; } if (parsedCookie?.domain) { @@ -99,7 +113,7 @@ export default function parseResponseReceivedExtraInfo( responseEvents: [ { type: RESPONSE_EVENT.CDP_RESPONSE_RECEIVED_EXTRA_INFO, - requestId: response.requestId, + requestId, url: url, blocked: blockedCookie ? true : false, timeStamp: Date.now(), @@ -113,6 +127,7 @@ export default function parseResponseReceivedExtraInfo( isFirstParty: isFirstParty(domain, tabUrl), headerType: 'response' as CookieData['headerType'], frameIdList: [], + exemptionReason: exemptedCookie?.exemptionReason, }; cookies.push(singleCookie); diff --git a/packages/design-system/package.json b/packages/design-system/package.json index da22940f8..7d9530651 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -1,6 +1,6 @@ { "name": "@ps-analysis-tool/design-system", - "version": "0.6.0", + "version": "0.7.0", "description": "A package for presentational components that are being used in multiple packages", "main": "./src/index.ts", "source":"./src/index.ts", diff --git a/packages/design-system/src/components/button/index.tsx b/packages/design-system/src/components/button/index.tsx index 41e5ce6dc..35f1c3033 100644 --- a/packages/design-system/src/components/button/index.tsx +++ b/packages/design-system/src/components/button/index.tsx @@ -21,100 +21,61 @@ import React from 'react'; interface ButtonProps { text: string | React.ReactNode; + title?: string; name?: string; onClick?: () => void; loading?: boolean; type?: 'button' | 'submit' | 'reset'; variant?: 'primary' | 'secondary' | 'danger' | 'small' | 'large'; extraClasses?: string; + disabled?: boolean; } const Button = ({ + title = '', text, name = 'button', onClick = undefined, type = 'button', variant = 'primary', extraClasses = '', + disabled = false, }: ButtonProps) => { - switch (variant) { - case 'small': - return ( - - ); - case 'large': - return ( - - ); - case 'primary': - return ( - - ); - case 'secondary': - return ( - - ); - case 'danger': - return ( - - ); - default: - return <>; - } + return ( + + ); }; export default Button; diff --git a/packages/design-system/src/components/circlePieChart/index.tsx b/packages/design-system/src/components/circlePieChart/index.tsx index 077a49c50..43107ca6c 100644 --- a/packages/design-system/src/components/circlePieChart/index.tsx +++ b/packages/design-system/src/components/circlePieChart/index.tsx @@ -32,6 +32,8 @@ interface CirclePieChartProps { title?: string; fallbackText?: string; infoIconClassName?: string; + centerTitleExtraClasses?: string; + pieChartExtraClasses?: string; } export const MAX_COUNT = 999; @@ -41,16 +43,18 @@ const CirclePieChart = ({ data, title, infoIconClassName = '', + centerTitleExtraClasses = '', + pieChartExtraClasses = '', }: CirclePieChartProps) => { const centerTitleClasses = centerCount <= MAX_COUNT ? 'text-2xl' : 'text-l'; return ( - <> +
{centerCount <= 0 ? ( ) : ( -
+
{centerCount <= MAX_COUNT ? centerCount : MAX_COUNT + '+'} @@ -85,7 +90,7 @@ const CirclePieChart = ({ )}
)} - +
); }; diff --git a/packages/design-system/src/components/cookieDetails/details.tsx b/packages/design-system/src/components/cookieDetails/details.tsx index dc83d889d..8087c99ce 100644 --- a/packages/design-system/src/components/cookieDetails/details.tsx +++ b/packages/design-system/src/components/cookieDetails/details.tsx @@ -21,6 +21,8 @@ import React, { useState } from 'react'; import classNames from 'classnames'; import { BLOCK_STATUS, + CookieData, + cookieExemptionReason, cookieIssueDetails, type CookieTableData, } from '@ps-analysis-tool/common'; @@ -87,6 +89,21 @@ const Details = ({ selectedCookie, isUsingCDP }: DetailsProps) => { return (
+ {selectedCookie.exemptionReason && + selectedCookie.exemptionReason.toLowerCase() !== 'none' && ( +
+

+ Exemption Reason +

+

+ { + cookieExemptionReason[ + selectedCookie.exemptionReason as CookieData['exemptionReason'] + ] + } +

+
+ )} {selectedCookie.isDomainInAllowList && (

@@ -100,19 +117,17 @@ const Details = ({ selectedCookie, isUsingCDP }: DetailsProps) => {

)} - {(outboundBlock || inboundBlock) && - hasValidBlockedReason && - isUsingCDP && ( - <> -

- Blocked Reason -

-

- - )} + {hasValidBlockedReason && isUsingCDP && ( + <> +

+ Blocked Reason +

+

+ + )} {selectedCookie?.blockingStatus?.inboundBlock === BLOCK_STATUS.BLOCKED_IN_SOME_EVENTS && diff --git a/packages/design-system/src/components/cookieTable/index.tsx b/packages/design-system/src/components/cookieTable/index.tsx index eb7025866..92465b00f 100644 --- a/packages/design-system/src/components/cookieTable/index.tsx +++ b/packages/design-system/src/components/cookieTable/index.tsx @@ -29,25 +29,19 @@ import { CookieTableData, getCookieKey } from '@ps-analysis-tool/common'; /** * Internal dependencies. */ -import { - Table, - TableColumn, - TableData, - TableFilter, - TableRow, - useTable, -} from '../table'; +import { Table, TableColumn, TableData, TableFilter, TableRow } from '../table'; +import { TableProvider } from '../table/useTable/provider'; +import { conditionalTableRowClassesHandler, exportCookies } from './utils'; interface CookieTableProps { - useIsBlockedToHighlight?: boolean; + queryIsBlockedToHighlight?: boolean; data: TableData[]; tableColumns: TableColumn[]; tableFilters?: TableFilter; tableSearchKeys?: string[]; tablePersistentSettingsKey?: string; + isFiltersSidebarOpen?: boolean; selectedFrame: string | null; - showTopBar?: boolean; - hideExport?: boolean; selectedFrameCookie: { [frame: string]: CookieTableData | null; } | null; @@ -56,11 +50,12 @@ interface CookieTableProps { [frame: string]: CookieTableData | null; } | null ) => void; - extraInterfaceToTopBar?: React.ReactNode; + extraInterfaceToTopBar?: () => React.JSX.Element; onRowContextMenu?: ( e: React.MouseEvent, row: TableRow ) => void; + hideExport?: boolean; } const CookieTable = forwardRef< @@ -70,19 +65,19 @@ const CookieTable = forwardRef< CookieTableProps >(function CookieTable( { - useIsBlockedToHighlight = false, + queryIsBlockedToHighlight = true, tableColumns, tableFilters, tableSearchKeys, tablePersistentSettingsKey, data: cookies, - showTopBar, - hideExport = false, + isFiltersSidebarOpen, selectedFrame, selectedFrameCookie, setSelectedFrameCookie, extraInterfaceToTopBar, onRowContextMenu, + hideExport, }: CookieTableProps, ref ) { @@ -108,6 +103,22 @@ const CookieTable = forwardRef< [selectedFrame, setSelectedFrameCookie] ); + const onRowContextMenuHandler = useCallback( + (e: React.MouseEvent, row: TableRow) => { + onRowContextMenu?.(e, row); + onRowClick(row?.originalData); + }, + [onRowClick, onRowContextMenu] + ); + + const getRowObjectKey = useCallback( + (row: TableRow) => + getCookieKey( + (row?.originalData as CookieTableData).parsedCookie + ) as string, + [] + ); + useImperativeHandle( ref, () => { @@ -120,18 +131,43 @@ const CookieTable = forwardRef< [onRowClick] ); - const selectedKey = useMemo( - () => Object.values(selectedFrameCookie ?? {})[0], - [selectedFrameCookie] + const selectedKey = useMemo(() => { + const key = Object.values(selectedFrameCookie ?? {})[0]; + + return key === null ? null : getCookieKey(key?.parsedCookie); + }, [selectedFrameCookie]); + + const _conditionalTableRowClassesHandler = useCallback( + (row: TableRow, isRowFocused: boolean, rowIndex: number) => { + return conditionalTableRowClassesHandler( + row, + isRowFocused, + rowIndex, + selectedKey, + queryIsBlockedToHighlight + ); + }, + [selectedKey, queryIsBlockedToHighlight] ); - const table = useTable({ - tableColumns, - data: cookies, - tableFilterData: tableFilters, - tableSearchKeys, - tablePersistentSettingsKey, - }); + const hasVerticalBar = useCallback((row: TableRow) => { + const isDomainInAllowList = (row.originalData as CookieTableData) + ?.isDomainInAllowList; + return Boolean(isDomainInAllowList); + }, []); + + const isRowSelected = useCallback( + (cookie: TableData | null) => { + const _cookie = cookie as CookieTableData; + if (!_cookie) { + return true; + } + + const tableCookieKey = getCookieKey(_cookie.parsedCookie); + return tableCookieKey === selectedKey; + }, + [selectedKey] + ); useEffect(() => { window.addEventListener('resize', () => forceUpdate()); @@ -140,31 +176,29 @@ const CookieTable = forwardRef< }; }, []); + // TODO: Move TableProvider and logic to one level up in extension and cli-dashboard, for allowing modularity. return (

-
- getCookieKey( - (row?.originalData as CookieTableData).parsedCookie - ) as string - } + , - row: TableRow - ) => { - onRowContextMenu?.(e, row); - onRowClick(row?.originalData); - }} - /> + onRowContextMenu={onRowContextMenuHandler} + getRowObjectKey={getRowObjectKey} + conditionalTableRowClassesHandler={_conditionalTableRowClassesHandler} + exportTableData={!hideExport ? exportCookies : undefined} + hasVerticalBar={hasVerticalBar} + isRowSelected={isRowSelected} + > +
+ ); }); diff --git a/packages/design-system/src/components/cookieTable/tests/index.tsx b/packages/design-system/src/components/cookieTable/tests/index.tsx index 53e957107..994852886 100644 --- a/packages/design-system/src/components/cookieTable/tests/index.tsx +++ b/packages/design-system/src/components/cookieTable/tests/index.tsx @@ -315,9 +315,12 @@ describe('CookieTable', () => { const test2Filter = await screen.findByText('test2Filter'); act(() => fireEvent.click(test2Filter)); + const test3Filter = await screen.findByText('test3Filter'); + act(() => fireEvent.click(test3Filter)); + await waitFor(() => { const rows = screen.getAllByTestId('body-row'); - expect(rows.length).toBe(2); + expect(rows.length).toBe(3); }); const clearFiltersButton = await screen.findByText('Clear all'); diff --git a/packages/design-system/src/components/cookieTable/utils/conditionalTableRowClassesHandler.ts b/packages/design-system/src/components/cookieTable/utils/conditionalTableRowClassesHandler.ts new file mode 100644 index 000000000..71821b47d --- /dev/null +++ b/packages/design-system/src/components/cookieTable/utils/conditionalTableRowClassesHandler.ts @@ -0,0 +1,69 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + BLOCK_STATUS, + CookieTableData, + getCookieKey, +} from '@ps-analysis-tool/common'; +import classnames from 'classnames'; + +import { TableRow } from '../../table'; + +// eslint-disable-next-line complexity +const conditionalTableRowClassesHandler = ( + row: TableRow, + isRowFocused: boolean, + rowIndex: number, + selectedKey: string | null, + queryIsBlockedToHighlight: boolean +) => { + const rowKey = getCookieKey( + (row?.originalData as CookieTableData).parsedCookie + ) as string; + const isBlocked = queryIsBlockedToHighlight + ? (row.originalData as CookieTableData)?.isBlocked + : (row.originalData as CookieTableData)?.blockingStatus?.inboundBlock !== + BLOCK_STATUS.NOT_BLOCKED || + (row.originalData as CookieTableData)?.blockingStatus?.outboundBlock !== + BLOCK_STATUS.NOT_BLOCKED; + const isDomainInAllowList = (row.originalData as CookieTableData) + ?.isDomainInAllowList; + + const tableRowClassName = classnames( + isBlocked && + (rowKey !== selectedKey + ? rowIndex % 2 + ? 'dark:bg-flagged-row-even-dark bg-flagged-row-even-light' + : 'dark:bg-flagged-row-odd-dark bg-flagged-row-odd-light' + : isRowFocused + ? 'bg-selection-yellow-dark dark:bg-selection-yellow-light' + : 'bg-royal-blue text-white dark:bg-medium-persian-blue dark:text-chinese-silver'), + isDomainInAllowList && + !isBlocked && + (rowKey !== selectedKey + ? rowIndex % 2 + ? 'dark:bg-jungle-green-dark bg-leaf-green-dark' + : 'dark:bg-jungle-green-light bg-leaf-green-light' + : isRowFocused + ? 'bg-selection-green-dark dark:bg-selection-green-light' + : 'bg-royal-blue text-white dark:bg-medium-persian-blue dark:text-chinese-silver') + ); + + return tableRowClassName; +}; + +export default conditionalTableRowClassesHandler; diff --git a/packages/design-system/src/components/cookieTable/utils/exportCookies.ts b/packages/design-system/src/components/cookieTable/utils/exportCookies.ts new file mode 100644 index 000000000..2c88458b8 --- /dev/null +++ b/packages/design-system/src/components/cookieTable/utils/exportCookies.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { CookieTableData } from '@ps-analysis-tool/common'; +import { saveAs } from 'file-saver'; + +/** + * Internal dependencies + */ +import { TableRow } from '../../table'; +import { generateCookieTableCSV } from '../../table/utils'; + +const exportCookies = (rows: TableRow[]) => { + const _cookies = rows.map(({ originalData }) => originalData); + if (_cookies.length > 0 && 'parsedCookie' in _cookies[0]) { + const csvTextBlob = generateCookieTableCSV(_cookies as CookieTableData[]); + saveAs(csvTextBlob, 'Cookies Report.csv'); + } +}; + +export default exportCookies; diff --git a/packages/design-system/src/components/cookieTable/utils/index.ts b/packages/design-system/src/components/cookieTable/utils/index.ts new file mode 100644 index 000000000..cf16d9027 --- /dev/null +++ b/packages/design-system/src/components/cookieTable/utils/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { default as exportCookies } from './exportCookies'; +export { default as conditionalTableRowClassesHandler } from './conditionalTableRowClassesHandler'; diff --git a/packages/design-system/src/components/cookiesLanding/cookieLandingHeaderContainer.tsx b/packages/design-system/src/components/cookiesLanding/cookiesLandingWrapper.tsx similarity index 64% rename from packages/design-system/src/components/cookiesLanding/cookieLandingHeaderContainer.tsx rename to packages/design-system/src/components/cookiesLanding/cookiesLandingWrapper.tsx index 6db89b044..631662f19 100644 --- a/packages/design-system/src/components/cookiesLanding/cookieLandingHeaderContainer.tsx +++ b/packages/design-system/src/components/cookiesLanding/cookiesLandingWrapper.tsx @@ -22,26 +22,44 @@ import React from 'react'; * Internal dependencies. */ import LandingHeader, { type DataMapping } from './landingHeader'; +import { InfoIcon } from '../../icons'; -export interface CookiesLandingContainerProps { +export interface CookiesLandingWrapperProps { dataMapping?: DataMapping[]; + infoIconTitle?: string | React.ReactNode; showLandingHeader?: boolean; testId?: string | null; children?: React.ReactNode; description?: React.ReactNode; } -const CookiesLandingContainer = ({ +const CookiesLandingWrapper = ({ dataMapping = [], + infoIconTitle = '', showLandingHeader = true, testId = 'cookie-landing-insights', description = '', children, -}: CookiesLandingContainerProps) => { +}: CookiesLandingWrapperProps) => { return (
- {showLandingHeader && } +
+ {showLandingHeader && } + {Boolean(infoIconTitle) && ( +
+
+ +
+

+ {infoIconTitle} +

+
+ )} +
{description && (

@@ -57,4 +75,4 @@ const CookiesLandingContainer = ({ ); }; -export default CookiesLandingContainer; +export default CookiesLandingWrapper; diff --git a/packages/design-system/src/components/cookiesLanding/cookiesMatrix/index.tsx b/packages/design-system/src/components/cookiesLanding/cookiesMatrix/index.tsx index 102abd0cb..88247388c 100644 --- a/packages/design-system/src/components/cookiesLanding/cookiesMatrix/index.tsx +++ b/packages/design-system/src/components/cookiesLanding/cookiesMatrix/index.tsx @@ -16,7 +16,7 @@ /** * External dependencies. */ -import React, { useState } from 'react'; +import React from 'react'; import { type TabCookies, type TabFrames, @@ -26,13 +26,10 @@ import { /** * Internal dependencies */ -import Matrix from '../../matrix'; import type { MatrixComponentProps } from '../../matrix/matrixComponent'; -import { InfoIcon } from '../../../icons'; -import MatrixComponentHorizontal, { - type MatrixComponentHorizontalProps, -} from '../../matrix/matrixComponent/matrixComponentHorizontal'; +import { type MatrixComponentHorizontalProps } from '../../matrix/matrixComponent/matrixComponentHorizontal'; import { LEGEND_DESCRIPTION } from '../../../constants'; +import MatrixContainer from '../../matrixContainer'; interface CookiesMatrixProps { tabCookies: TabCookies | null; @@ -42,14 +39,13 @@ interface CookiesMatrixProps { description?: string; showHorizontalMatrix?: boolean; showMatrix?: boolean; - showInfoIcon?: boolean; count?: number | null; associatedCookiesCount?: number | null; allowExpand?: boolean; highlightTitle?: boolean; capitalizeTitle?: boolean; - infoIconTitle?: string; matrixHorizontalData?: MatrixComponentHorizontalProps[] | null; + infoIconTitle?: string; } const CookiesMatrix = ({ @@ -60,16 +56,14 @@ const CookiesMatrix = ({ description = '', showHorizontalMatrix = true, showMatrix = true, - showInfoIcon = true, count = null, associatedCookiesCount = null, allowExpand = true, highlightTitle = false, capitalizeTitle = false, matrixHorizontalData = null, - infoIconTitle = 'Cookies must be analyzed on a new, clean Chrome profile for an accurate report.', + infoIconTitle, }: CookiesMatrixProps) => { - const [isExpanded, setIsExpanded] = useState(false); const dataComponents: MatrixComponentProps[] = []; componentData.forEach((component) => { @@ -78,7 +72,6 @@ const CookiesMatrix = ({ ...component, description: legendDescription, title: component.label, - isExpanded, containerClasses: '', }); }); @@ -87,13 +80,12 @@ const CookiesMatrix = ({ const framesWithCookies = filterFramesWithCookies(tabCookies, tabFrames); const matrixHorizontalComponents = matrixHorizontalData - ? matrixHorizontalData.map((data) => ({ ...data, expand: isExpanded })) + ? matrixHorizontalData : [ { title: 'Number of Frames', description: 'Number of unique frames found across the page(s).', count: totalFrames, - expand: isExpanded, }, { title: 'Number of Frames with Associated Cookies', @@ -104,54 +96,25 @@ const CookiesMatrix = ({ : framesWithCookies ? Object.keys(framesWithCookies).length : 0, - expand: isExpanded, }, ]; + return (

-
-
-
-

- {title} - {showInfoIcon && ( - - - - )} - {count !== null && : {Number(count) || 0}} -

-

- {description} -

-
- {allowExpand && ( -

- -

- )} -
- {showMatrix && } -
- {showHorizontalMatrix && ( -
- {matrixHorizontalComponents.map( - (matrixHorizontalComponent, index) => ( - - ) - )} -
- )} +
); }; diff --git a/packages/design-system/src/components/cookiesLanding/cookiesMatrix/tests/cookieMatrix.tsx b/packages/design-system/src/components/cookiesLanding/cookiesMatrix/tests/cookieMatrix.tsx index 79e43674f..7c1f2c681 100644 --- a/packages/design-system/src/components/cookiesLanding/cookiesMatrix/tests/cookieMatrix.tsx +++ b/packages/design-system/src/components/cookiesLanding/cookiesMatrix/tests/cookieMatrix.tsx @@ -17,7 +17,7 @@ * External dependencies. */ import React from 'react'; -import { render } from '@testing-library/react'; +import { screen, render } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; /** @@ -28,14 +28,14 @@ import mockResponse from '../../../../test-data/cookieMockData'; import cookiesStatsComponents from '../../../../test-data/cookiesStatsComponents'; describe('CookiesMatrix', () => { - it('should render the cookies insights', () => { + it('should render the cookies insights', async () => { const tabCookies = mockResponse.tabCookies; const tabFrames = mockResponse.tabFrames; const title = 'Title'; - const { getByTestId } = render( + const { getByTestId, findByText, findAllByText } = render( { ); expect(getByTestId(`cookies-matrix-${title}`)).toBeInTheDocument(); + + const expandButton = await screen.findByTestId('expand-button'); + + expect(expandButton).toBeInTheDocument(); + + expandButton.click(); + + expect(await findByText('Functional')).toBeInTheDocument(); + expect((await findAllByText('1'))[0]).toHaveClass( + cookiesStatsComponents.legend[0].countClassName + ); }); }); diff --git a/packages/design-system/src/components/cookiesLanding/index.tsx b/packages/design-system/src/components/cookiesLanding/index.tsx index cf406c2a6..b64405b98 100644 --- a/packages/design-system/src/components/cookiesLanding/index.tsx +++ b/packages/design-system/src/components/cookiesLanding/index.tsx @@ -17,157 +17,27 @@ * External dependencies. */ import React from 'react'; -import type { TabCookies, TabFrames } from '@ps-analysis-tool/common'; -/** - * Internal dependencies. - */ -import MessageBox from '../messageBox'; -import { type DataMapping } from './landingHeader'; -import CookiesMatrix from './cookiesMatrix'; -import CookiesLandingContainer from './cookieLandingHeaderContainer'; -import { - prepareCookieStatsComponents, - prepareCookiesCount, - prepareFrameStatsComponent, -} from '../../utils'; +export type CookiesLandingSection = { + name: string; + link: string; + panel: { + Element: ((props: any) => React.JSX.Element) | React.NamedExoticComponent; + props?: Record; + }; +}; interface CookiesLandingProps { - tabFrames: TabFrames | null; - tabCookies: TabCookies | null; children?: React.ReactNode; - showInfoIcon?: boolean; - showBlockedInfoIcon?: boolean; - showHorizontalMatrix?: boolean; - associatedCookiesCount?: number | null; - showMessageBoxBody?: boolean; - showBlockedCookiesSection?: boolean; - additionalComponents?: { - [key: string]: React.FunctionComponent; - }; - showFramesSection?: boolean; - description?: React.ReactNode; - cookieClassificationTitle?: string; } -const CookiesLanding = ({ - tabCookies, - tabFrames, - children, - showInfoIcon = true, - showBlockedInfoIcon = true, - associatedCookiesCount = null, - showMessageBoxBody = true, - showBlockedCookiesSection = false, - showFramesSection = false, - showHorizontalMatrix = false, - description = '', - additionalComponents = {}, - cookieClassificationTitle, -}: CookiesLandingProps) => { - const cookieStats = prepareCookiesCount(tabCookies); - const cookiesStatsComponents = prepareCookieStatsComponents(cookieStats); - const frameStateCreator = prepareFrameStatsComponent(tabFrames, tabCookies); - const cookieClassificationDataMapping: DataMapping[] = [ - { - title: 'Total cookies', - count: cookieStats.total, - data: cookiesStatsComponents.legend, - }, - { - title: '1st party cookies', - count: cookieStats.firstParty.total, - data: cookiesStatsComponents.firstParty, - }, - { - title: '3rd party cookies', - count: cookieStats.thirdParty.total, - data: cookiesStatsComponents.thirdParty, - }, - ]; - - const blockedCookieDataMapping: DataMapping[] = [ - { - title: 'Blocked cookies', - count: cookieStats.blockedCookies.total, - data: cookiesStatsComponents.blocked, - }, - ]; - +const CookiesLanding = ({ children }: CookiesLandingProps) => { return (
- - {!cookieStats || - (cookieStats?.firstParty.total === 0 && - cookieStats?.thirdParty.total === 0 && ( - - ))} - - - {showBlockedCookiesSection && ( - - {cookiesStatsComponents.blockedCookiesLegend.length > 0 && ( - <> - - - )} - {children &&
{children}
} -
- )} - {/* TODO: This is not scalable. Refactor code so that components can be added from the the extension or dashboard package. */} - {Boolean(Object.keys(additionalComponents).length) && - Object.keys(additionalComponents).map((key: string) => { - const Component = additionalComponents[key]; - return ; - })} - {showFramesSection && ( - - - - )} + {children}
); }; diff --git a/packages/design-system/src/components/cookiesLanding/landingHeader/index.tsx b/packages/design-system/src/components/cookiesLanding/landingHeader/index.tsx index 5123927a4..d09fa7ee2 100644 --- a/packages/design-system/src/components/cookiesLanding/landingHeader/index.tsx +++ b/packages/design-system/src/components/cookiesLanding/landingHeader/index.tsx @@ -18,15 +18,8 @@ */ import React from 'react'; import { CirclePieChart } from '@ps-analysis-tool/design-system'; - -export interface DataMapping { - title: string; - count: number; - data: { - count: number; - color: string; - }[]; -} +import { DataMapping } from '@ps-analysis-tool/common'; +import classnames from 'classnames'; interface LandingHeaderProps { dataMapping?: DataMapping[]; @@ -36,20 +29,40 @@ const LandingHeader = ({ dataMapping = [] }: LandingHeaderProps) => { return (
{dataMapping.map((circleData, index) => { return ( -
- +
+
); })} diff --git a/packages/design-system/src/components/cookiesLanding/landingHeader/tests/landingHeader.tsx b/packages/design-system/src/components/cookiesLanding/landingHeader/tests/landingHeader.tsx index 0ba8877bf..b2947463a 100644 --- a/packages/design-system/src/components/cookiesLanding/landingHeader/tests/landingHeader.tsx +++ b/packages/design-system/src/components/cookiesLanding/landingHeader/tests/landingHeader.tsx @@ -17,7 +17,7 @@ * External dependencies. */ import React from 'react'; -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; /** @@ -46,8 +46,10 @@ describe('LandingHeader', () => { data: [...cookiesStatsComponents.thirdParty], }, ]; - const { getByTestId } = render(); + render(); - expect(getByTestId('cookies-landing-header')).toBeInTheDocument(); + expect(screen.getByTestId('cookies-landing-header')).toBeInTheDocument(); + expect(screen.getByText('Total Cookies')).toBeInTheDocument(); + expect(screen.getByText('999+')).toBeInTheDocument(); }); }); diff --git a/packages/design-system/src/components/cookiesLanding/tests/useFiltersMapping.tsx b/packages/design-system/src/components/cookiesLanding/tests/useFiltersMapping.tsx new file mode 100644 index 000000000..e78b3c582 --- /dev/null +++ b/packages/design-system/src/components/cookiesLanding/tests/useFiltersMapping.tsx @@ -0,0 +1,66 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TabFrames } from '@ps-analysis-tool/common'; +import { renderHook } from '@testing-library/react'; +import useFiltersMapping from '../useFiltersMapping'; +import { useSidebar } from '../../sidebar/useSidebar'; + +jest.mock('../../sidebar/useSidebar', () => ({ + useSidebar: jest.fn(), +})); +const mockUseSidebar = useSidebar as jest.Mock; + +describe('useFiltersMapping', () => { + it('shoule return correct query object', () => { + // Arrange + const tabFrames = { + frame1: { + frameIds: [], + }, + frame2: { + frameIds: [], + }, + } as TabFrames; + const updateSelectedItemKey = jest.fn(); + + mockUseSidebar.mockReturnValue(updateSelectedItemKey); + + // Act + const { result } = renderHook(() => useFiltersMapping(tabFrames)); + + // Assert + expect(result.current.selectedItemUpdater).toBeDefined(); + + // Act + result.current.selectedItemUpdater('title', 'accessorKey'); + + // Assert + expect(updateSelectedItemKey).toHaveBeenCalledWith( + 'frame1', + '{"filter":{"accessorKey":["title"]}}' + ); + + // Act + result.current.selectedItemUpdater('title'); + + // Assert + expect(updateSelectedItemKey).toHaveBeenCalledWith( + 'frame1', + '{"filter":{}}' + ); + }); +}); diff --git a/packages/design-system/src/components/cookiesLanding/useFiltersMapping.tsx b/packages/design-system/src/components/cookiesLanding/useFiltersMapping.tsx new file mode 100644 index 000000000..ff39fab44 --- /dev/null +++ b/packages/design-system/src/components/cookiesLanding/useFiltersMapping.tsx @@ -0,0 +1,62 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies. + */ +import { useCallback, useMemo } from 'react'; +import { useSidebar } from '@ps-analysis-tool/design-system'; +import type { TabFrames } from '@ps-analysis-tool/common'; + +/** + * Hook to expose the selectedItemUpdater function. + * This function is used to update the selected item key and set the query object. + * @param tabFrames Tab frames to get the first frame from. + * @returns Object containing the selectedItemUpdater function. + */ +const useFiltersMapping = (tabFrames: TabFrames) => { + const firstFrame = useMemo( + () => Object.keys(tabFrames || {})?.[0] || '', + [tabFrames] + ); + + const updateSelectedItemKey = useSidebar( + ({ actions }) => actions.updateSelectedItemKey + ); + + const selectedItemUpdater = useCallback( + (title: string, accessorKey?: string) => { + const queryObject = accessorKey + ? { + [accessorKey]: [title], + } + : {}; + + const modifiedQuery = { + filter: { + ...queryObject, + }, + }; + + updateSelectedItemKey(firstFrame, JSON.stringify(modifiedQuery)); + }, + [firstFrame, updateSelectedItemKey] + ); + + return { selectedItemUpdater }; +}; + +export default useFiltersMapping; diff --git a/packages/design-system/src/components/errorFallback/extensionReloadNotification.tsx b/packages/design-system/src/components/errorFallback/extensionReloadNotification.tsx index fce416c01..d5bcf19d9 100644 --- a/packages/design-system/src/components/errorFallback/extensionReloadNotification.tsx +++ b/packages/design-system/src/components/errorFallback/extensionReloadNotification.tsx @@ -22,15 +22,33 @@ import React from 'react'; * Internal dependencies. */ import Button from '../button'; +interface ExtensionReloadNotificationProps { + tabId?: number; +} -const ExtensionReloadNotification = () => { +const ExtensionReloadNotification = ({ + tabId, +}: ExtensionReloadNotificationProps) => { return (

Looks like extension has been updated since devtool was open.

-
); diff --git a/packages/design-system/src/components/index.ts b/packages/design-system/src/components/index.ts index 9c089bdae..55209a6e1 100644 --- a/packages/design-system/src/components/index.ts +++ b/packages/design-system/src/components/index.ts @@ -27,6 +27,8 @@ export { default as Matrix } from './matrix'; export { default as MatrixComponentHorizontal } from './matrix/matrixComponent/matrixComponentHorizontal'; export type { MatrixComponentProps } from './matrix/matrixComponent'; export { default as CookiesLanding } from './cookiesLanding'; +export * from './cookiesLanding'; +export type { DataMapping } from './cookiesLanding/landingHeader'; export { default as CookiesMatrix } from './cookiesLanding/cookiesMatrix'; export { default as CookieDetails } from './cookieDetails'; export { default as Details } from './cookieDetails/details'; @@ -42,6 +44,10 @@ export * from './sidebar'; export { default as InspectButton } from './inspectButton'; export { default as ToastMessage } from './toastMessage'; export { - default as CookiesLandingContainer, - type CookiesLandingContainerProps, -} from './cookiesLanding/cookieLandingHeaderContainer'; + default as CookiesLandingWrapper, + type CookiesLandingWrapperProps, +} from './cookiesLanding/cookiesLandingWrapper'; +export { default as MatrixContainer } from './matrixContainer'; +export { default as MenuBar } from './menuBar'; +export * from './menuBar'; +export { default as useFiltersMapping } from './cookiesLanding/useFiltersMapping'; diff --git a/packages/design-system/src/components/landingPage/index.tsx b/packages/design-system/src/components/landingPage/index.tsx index 093f6be79..96c593431 100644 --- a/packages/design-system/src/components/landingPage/index.tsx +++ b/packages/design-system/src/components/landingPage/index.tsx @@ -99,7 +99,7 @@ const LandingPage = ({ /> )} {psInfoKey && } -
{contentPanel}
+ {contentPanel &&
{contentPanel}
}
{children && ( diff --git a/packages/design-system/src/components/matrix/index.tsx b/packages/design-system/src/components/matrix/index.tsx index 11ba3974f..356d11f9f 100644 --- a/packages/design-system/src/components/matrix/index.tsx +++ b/packages/design-system/src/components/matrix/index.tsx @@ -17,6 +17,7 @@ * External dependencies. */ import React from 'react'; +import classnames from 'classnames'; /** * Internal dependencies. @@ -25,9 +26,10 @@ import MatrixComponent, { type MatrixComponentProps } from './matrixComponent'; interface MatrixProps { dataComponents: MatrixComponentProps[]; + expand?: boolean; } -const Matrix = ({ dataComponents }: MatrixProps) => { +const Matrix = ({ dataComponents, expand }: MatrixProps) => { if (!dataComponents || !dataComponents.length) { return null; } @@ -40,16 +42,32 @@ const Matrix = ({ dataComponents }: MatrixProps) => { index === dataComponents.length - 1 || index === dataComponents.length - 2; return ( - + className={classnames( + 'py-1 border-bright-gray dark:border-quartz', + { + 'border-b': !isLastTwoItems, + } + )} + > + +
); } return null; diff --git a/packages/design-system/src/components/matrix/matrixComponent/index.tsx b/packages/design-system/src/components/matrix/matrixComponent/index.tsx index 8fb3c38cc..bd93af98b 100644 --- a/packages/design-system/src/components/matrix/matrixComponent/index.tsx +++ b/packages/design-system/src/components/matrix/matrixComponent/index.tsx @@ -17,6 +17,7 @@ * External dependencies. */ import React from 'react'; +import classnames from 'classnames'; /** * Internal dependencies. @@ -27,10 +28,10 @@ export interface MatrixComponentProps { color: string; title: string; description?: string; + onClick?: (title: string) => void; count: number; isExpanded?: boolean; countClassName: string; - containerClasses: string; } const MatrixComponent = ({ @@ -40,17 +41,16 @@ const MatrixComponent = ({ count, isExpanded = false, countClassName, - containerClasses, }: MatrixComponentProps) => { return ( -
+
-
+

{title}

-

+

{count}

{description && isExpanded && ( diff --git a/packages/design-system/src/components/matrix/tests/matrix.tsx b/packages/design-system/src/components/matrix/tests/matrix.tsx index c9e257928..0e61147ce 100644 --- a/packages/design-system/src/components/matrix/tests/matrix.tsx +++ b/packages/design-system/src/components/matrix/tests/matrix.tsx @@ -32,28 +32,37 @@ describe('Matrix', () => { expect(container.firstChild).toBeNull(); }); - it('renders MatrixComponent for each dataComponent', () => { + it('renders MatrixComponent for each dataComponent', async () => { const mockDataComponents: MatrixComponentProps[] = [ { color: 'red', title: 'Test title', count: 10, - countClassName: '', + countClassName: 'some-class', containerClasses: '', + description: 'Test description', }, { color: 'blue', - title: 'Test title', + title: 'Test title1', count: 10, - countClassName: '', + countClassName: 'some-class-2', containerClasses: '', }, ]; - render(); + const { rerender } = render( + + ); const matrixContainer = screen.getByTestId('matrix'); expect(matrixContainer).toBeInTheDocument(); + + expect(await screen.findByText('Test title')).toBeInTheDocument(); + + rerender(); + + expect(await screen.findByText('Test description')).toBeInTheDocument(); }); }); diff --git a/packages/design-system/src/components/matrixContainer/index.tsx b/packages/design-system/src/components/matrixContainer/index.tsx new file mode 100644 index 000000000..088af620a --- /dev/null +++ b/packages/design-system/src/components/matrixContainer/index.tsx @@ -0,0 +1,117 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * External dependencies. + */ +import React, { useState } from 'react'; +import classname from 'classnames'; + +/** + * Internal dependencies + */ +import { ChevronDown, InfoIcon } from '../../icons'; +import Matrix from '../matrix'; +import MatrixComponentHorizontal, { + MatrixComponentHorizontalProps, +} from '../matrix/matrixComponent/matrixComponentHorizontal'; +import { MatrixComponentProps } from '../matrix/matrixComponent'; + +interface MatrixContainerProps { + matrixData?: MatrixComponentProps[]; + horizontalMatrixData?: MatrixComponentHorizontalProps[]; + title?: string; + description?: string; + showMatrix?: boolean; + count?: number | null; + allowExpand?: boolean; + highlightTitle?: boolean; + capitalizeTitle?: boolean; + infoIconTitle?: string; +} + +const MatrixContainer = ({ + matrixData = [], + horizontalMatrixData = [], + title = 'Categories', + description = '', + showMatrix = true, + count = null, + allowExpand = true, + highlightTitle = false, + capitalizeTitle = false, + infoIconTitle, +}: MatrixContainerProps) => { + const [isExpanded, setIsExpanded] = useState(false); + + return ( +
+
+
+ {allowExpand && ( +

+ +

+ )} +
+

+ {title} + {Boolean(infoIconTitle) && ( + + + + )} + {count !== null && : {Number(count) || 0}} +

+

+ {description} +

+
+
+ {showMatrix && ( + + )} +
+ {Boolean(horizontalMatrixData.length) && ( +
+ {horizontalMatrixData.map((data, index) => ( + + ))} +
+ )} +
+ ); +}; + +export default MatrixContainer; diff --git a/packages/design-system/src/components/menuBar/index.tsx b/packages/design-system/src/components/menuBar/index.tsx new file mode 100644 index 000000000..83d22ed3b --- /dev/null +++ b/packages/design-system/src/components/menuBar/index.tsx @@ -0,0 +1,162 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies. + */ +import React, { useEffect, useState } from 'react'; +import classnames from 'classnames'; +import { Export } from '@ps-analysis-tool/design-system'; + +/** + * Internal dependencies. + */ +import { isInCenter } from './utils'; + +export type MenuData = Array<{ + name: string; + link: string; +}>; + +interface MenuBarProps { + disableReportDownload?: boolean; + downloadReport?: () => void; + menuData: MenuData; + extraClasses?: string; + scrollContainerId: string; +} + +const MenuBar = ({ + disableReportDownload = true, + downloadReport, + menuData, + extraClasses, + scrollContainerId, +}: MenuBarProps) => { + const [selectedItem, setSelectedItem] = useState(menuData[0].link); + const [isListenerDisabled, setIsListenerDisabled] = useState(false); + + useEffect(() => { + let timeout: NodeJS.Timeout; + const element = document.getElementById(selectedItem); + if (element) { + element.scrollIntoView?.({ behavior: 'smooth' }); + timeout = setTimeout(() => { + setIsListenerDisabled(false); + }, 700); + } + return () => { + if (timeout) { + clearTimeout(timeout); + } + }; + }, [selectedItem]); + + useEffect(() => { + const scrollContainer = document.getElementById(scrollContainerId); + const firstItemLink = menuData[0].link; + const lastItemLink = menuData[menuData.length - 1].link; + + const handleScroll = () => { + if (isListenerDisabled || !scrollContainer) { + return; + } + + const distanceScrolled = scrollContainer.scrollTop; + const maxScrollDistance = + scrollContainer.scrollHeight - scrollContainer.clientHeight; + + menuData.forEach(({ link: id }) => { + const section = document.getElementById(id); + setSelectedItem((prev) => { + if (scrollContainer?.scrollTop === 0) { + return firstItemLink; + } else if (section && isInCenter(section) && prev !== id) { + return id; + } else if (maxScrollDistance - distanceScrolled < 5) { + return lastItemLink; + } + return prev; + }); + }); + }; + + scrollContainer?.addEventListener('scroll', handleScroll); + return () => { + scrollContainer?.removeEventListener('scroll', handleScroll); + }; + }, [menuData, isListenerDisabled, scrollContainerId]); + + return ( + + ); +}; + +export default MenuBar; diff --git a/packages/design-system/src/components/menuBar/utils/index.ts b/packages/design-system/src/components/menuBar/utils/index.ts new file mode 100644 index 000000000..0b96114b6 --- /dev/null +++ b/packages/design-system/src/components/menuBar/utils/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { default as isInCenter } from './isInCenter'; diff --git a/packages/design-system/src/components/menuBar/utils/isInCenter.ts b/packages/design-system/src/components/menuBar/utils/isInCenter.ts new file mode 100644 index 000000000..0481a779b --- /dev/null +++ b/packages/design-system/src/components/menuBar/utils/isInCenter.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Determines if an HTMLElement is vertically centered within the viewport. + * @param {HTMLElement} element - The HTMLElement to check for vertical centering. + * @returns {boolean} True if the element is vertically centered within the viewport, false otherwise. + */ +const isInCenter = (element: HTMLElement) => { + const rect = element.getBoundingClientRect(); + const viewportHeight = + window.innerHeight || document.documentElement.clientHeight; + + const elementTop = rect.top; + const elementBottom = rect.bottom; + const elementHeight = elementBottom - elementTop; + + const elementCenterY = elementTop + elementHeight / 2; + const viewportCenterY = viewportHeight / 2; + + return Math.abs(elementCenterY - viewportCenterY) <= elementHeight / 2; +}; + +export default isInCenter; diff --git a/packages/design-system/src/components/progressBar/index.tsx b/packages/design-system/src/components/progressBar/index.tsx index 436898cfa..ef7d10992 100644 --- a/packages/design-system/src/components/progressBar/index.tsx +++ b/packages/design-system/src/components/progressBar/index.tsx @@ -24,6 +24,7 @@ interface ProgressBarProps { const ProgressBar = ({ additionalStyles = '' }: ProgressBarProps) => { return (
diff --git a/packages/design-system/src/components/sidebar/index.ts b/packages/design-system/src/components/sidebar/index.ts index 8b6e8399e..3e45714bd 100644 --- a/packages/design-system/src/components/sidebar/index.ts +++ b/packages/design-system/src/components/sidebar/index.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -export { default as useSidebar } from './useSidebar'; export * from './useSidebar'; +export * from './useSidebar/constants'; export { default as Sidebar } from './sidebar'; diff --git a/packages/design-system/src/components/sidebar/sidebar.tsx b/packages/design-system/src/components/sidebar/sidebar.tsx index ac59ce7df..6faa920fd 100644 --- a/packages/design-system/src/components/sidebar/sidebar.tsx +++ b/packages/design-system/src/components/sidebar/sidebar.tsx @@ -23,36 +23,20 @@ import React, { useEffect, useRef, useState } from 'react'; * Internal dependencies. */ import SidebarChild from './sidebarChild'; -import type { SidebarItems } from './useSidebar'; +import { useSidebar } from './useSidebar'; interface SidebarProps { - selectedItemKey: string | null; - sidebarItems: SidebarItems; - isSidebarFocused: boolean; - setIsSidebarFocused: React.Dispatch; - updateSelectedItemKey: (key: string | null) => void; - onKeyNavigation: ( - event: React.KeyboardEvent, - key: string | null - ) => void; - toggleDropdown: (action: boolean, key: string) => void; - isKeyAncestor: (key: string) => boolean; - isKeySelected: (key: string) => boolean; visibleWidth?: number; } -const Sidebar = ({ - selectedItemKey, - sidebarItems, - isSidebarFocused, - setIsSidebarFocused, - updateSelectedItemKey, - onKeyNavigation, - toggleDropdown, - isKeyAncestor, - isKeySelected, - visibleWidth, -}: SidebarProps) => { +const Sidebar = ({ visibleWidth }: SidebarProps) => { + const { sidebarItems, setIsSidebarFocused } = useSidebar( + ({ state, actions }) => ({ + sidebarItems: state.sidebarItems, + setIsSidebarFocused: actions.setIsSidebarFocused, + }) + ); + const [didUserInteract, setDidUserInteract] = useState(false); const sidebarContainerRef = useRef(null); @@ -74,22 +58,17 @@ const Sidebar = ({ }, [setIsSidebarFocused]); return ( -
+
{Object.entries(sidebarItems).map(([itemKey, sidebarItem]) => ( diff --git a/packages/design-system/src/components/sidebar/sidebarChild.tsx b/packages/design-system/src/components/sidebar/sidebarChild.tsx index cf3e161af..243213228 100644 --- a/packages/design-system/src/components/sidebar/sidebarChild.tsx +++ b/packages/design-system/src/components/sidebar/sidebarChild.tsx @@ -21,49 +21,52 @@ import { ArrowDown, ArrowDownWhite, InfoIcon, + SidebarItemValue, + useSidebar, } from '@ps-analysis-tool/design-system'; /** * Internal dependencies. */ -import type { SidebarItemValue } from './useSidebar'; interface SidebarItemProps { - selectedItemKey: string | null; didUserInteract: boolean; setDidUserInteract: (didUserInteract: boolean) => void; itemKey: string; sidebarItem: SidebarItemValue; - isSidebarFocused: boolean; - setIsSidebarFocused: React.Dispatch; - updateSelectedItemKey: (key: string | null) => void; - onKeyNavigation: ( - event: React.KeyboardEvent, - key: string | null - ) => void; - toggleDropdown: (action: boolean, key: string) => void; - isKeyAncestor: (key: string) => boolean; - isKeySelected: (key: string) => boolean; recursiveStackIndex?: number; visibleWidth?: number; } +// eslint-disable-next-line complexity const SidebarChild = ({ - selectedItemKey, didUserInteract, setDidUserInteract, itemKey, sidebarItem, - isSidebarFocused, - setIsSidebarFocused, - updateSelectedItemKey, - onKeyNavigation, - toggleDropdown, - isKeyAncestor, - isKeySelected, recursiveStackIndex = 0, visibleWidth, }: SidebarItemProps) => { + const { + selectedItemKey, + isSidebarFocused, + setIsSidebarFocused, + updateSelectedItemKey, + toggleDropdown, + isKeyAncestor, + isKeySelected, + onKeyNavigation, + } = useSidebar(({ state, actions }) => ({ + selectedItemKey: state.selectedItemKey, + isSidebarFocused: state.isSidebarFocused, + setIsSidebarFocused: actions.setIsSidebarFocused, + updateSelectedItemKey: actions.updateSelectedItemKey, + toggleDropdown: actions.toggleDropdown, + isKeyAncestor: actions.isKeyAncestor, + isKeySelected: actions.isKeySelected, + onKeyNavigation: actions.onKeyNavigation, + })); + const itemRef = useRef(null); useEffect(() => { @@ -78,6 +81,10 @@ const SidebarChild = ({ setIsSidebarFocused, ]); + const SelectedIcon = sidebarItem.selectedIcon?.Element; + const Icon = sidebarItem.icon?.Element; + const ExtraInterfaceToTitle = sidebarItem.extraInterfaceToTitle?.Element; + return ( <> {/* SidebarItem */} @@ -102,6 +109,7 @@ const SidebarChild = ({ : 'bg-white dark:bg-raisin-black' } cursor-pointer ${sidebarItem.isBlurred ? 'opacity-50' : ''}`} style={{ paddingLeft: recursiveStackIndex * 16 + 12 }} + data-testid="sidebar-child" > {Object.keys(sidebarItem.children)?.length !== 0 && (
+ {/* Sidebar item's children */} <> {Object.keys(sidebarItem.children)?.length !== 0 && sidebarItem.dropdownOpen && ( @@ -162,18 +176,10 @@ const SidebarChild = ({ {Object.entries(sidebarItem.children).map(([childKey, child]) => ( diff --git a/packages/design-system/src/components/sidebar/tests/sidebar.tsx b/packages/design-system/src/components/sidebar/tests/sidebar.tsx new file mode 100644 index 000000000..9504b23c1 --- /dev/null +++ b/packages/design-system/src/components/sidebar/tests/sidebar.tsx @@ -0,0 +1,140 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import Sidebar from '../sidebar'; +import { useSidebar } from '../useSidebar'; + +jest.mock('../useSidebar', () => ({ + useSidebar: jest.fn(), +})); +const mockUseSidebar = useSidebar as jest.Mock; +const initialState = { + sidebarItems: { + item1: { + title: 'item1', + children: { + item2: { + title: 'item2', + children: {}, + }, + }, + }, + }, + setIsSidebarFocused: jest.fn(), + selectedItemKey: '', + isSidebarFocused: false, + setISidebarFocused: jest.fn(), + updateSelectedItemKey: jest.fn(), + toggleDropdown: jest.fn(), + isKeyAncestor: jest.fn(), + isKeySelected: jest.fn(), + onKeyNavigation: jest.fn(), +}; + +describe('Sidebar', () => { + beforeEach(() => { + mockUseSidebar.mockReturnValue(initialState); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should render correctly', async () => { + // Arrange + const props = { + visibleWidth: 200, + }; + + // Act + render(); + + // Assert + const sidebar = await screen.findByTestId('sidebar'); + expect(sidebar).toBeInTheDocument(); + }); + + it('should call setIsSidebarFocused when clicking outside the sidebar', async () => { + // Arrange + const props = { + visibleWidth: 200, + }; + + // Act + render(); + + // Assert + expect(initialState.setIsSidebarFocused).not.toHaveBeenCalled(); + + // Act + document.dispatchEvent(new MouseEvent('click')); + + // Assert + expect(initialState.setIsSidebarFocused).toHaveBeenCalled(); + expect(initialState.setIsSidebarFocused).toHaveBeenCalledWith(false); + + const sidebar = await screen.findByTestId('sidebar-child'); + expect(sidebar).toBeInTheDocument(); + + // Act + fireEvent.click(sidebar); + + // Assert + expect(initialState.setIsSidebarFocused).toHaveBeenCalledWith(true); + }); + + it('should handle callbacks', async () => { + // Arrange + const props = { + visibleWidth: 200, + }; + + // Act + render(); + + // Assert + expect(initialState.updateSelectedItemKey).not.toHaveBeenCalled(); + + const sidebar = await screen.findByTestId('sidebar-child'); + expect(sidebar).toBeInTheDocument(); + + // Act + fireEvent.click(sidebar); + + // Assert + expect(initialState.updateSelectedItemKey).toHaveBeenCalled(); + expect(initialState.updateSelectedItemKey).toHaveBeenCalledWith('item1'); + + // Act + const dropdown = await screen.findByTestId('sidebar-child-dropdown'); + fireEvent.click(dropdown); + + // Assert + expect(initialState.toggleDropdown).toHaveBeenCalled(); + expect(initialState.toggleDropdown).toHaveBeenCalledWith(true, 'item1'); + + // Act + fireEvent.keyDown(sidebar, { key: 'ArrowDown' }); + + // Assert + expect(initialState.onKeyNavigation).toHaveBeenCalled(); + expect(initialState.onKeyNavigation).toHaveBeenCalledWith( + expect.any(Object), + 'item1' + ); + }); +}); diff --git a/packages/design-system/src/components/sidebar/useSidebar/constants.ts b/packages/design-system/src/components/sidebar/useSidebar/constants.ts new file mode 100644 index 000000000..b86fb4cb1 --- /dev/null +++ b/packages/design-system/src/components/sidebar/useSidebar/constants.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum SIDEBAR_ITEMS_KEYS { + COOKIES = 'cookies', + COOKIES_WITH_ISSUES = 'cookie-issues', + TECHNOLOGIES = 'technologies', + PRIVACY_SANDBOX = 'privacy-sandbox', + SITE_BOUNDARIES = 'site-boundaries', + CHIPS = 'chips', + RELATED_WEBSITE_SETS = 'related-website-sets', + PRIVATE_ADVERTISING = 'private-advertising', + TOPICS = 'topics', + ATTRIBUTION = 'attribution', + ANTI_COVERT_TRACKING = 'anti-covert-tracking', + BOUNCE_TRACKING = 'bounce-tracking', + FINGERPRINTING = 'fingerprinting', + FACILITATED_TESTING = 'facilitated-testing', + SETTINGS = 'settings', +} diff --git a/packages/design-system/src/components/sidebar/useSidebar/context.ts b/packages/design-system/src/components/sidebar/useSidebar/context.ts new file mode 100644 index 000000000..46d190b3a --- /dev/null +++ b/packages/design-system/src/components/sidebar/useSidebar/context.ts @@ -0,0 +1,77 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies. + */ +import { createContext, noop } from '@ps-analysis-tool/common'; + +/** + * Internal dependencies. + */ +import { SidebarComponent, SidebarItems } from './types'; + +export interface SidebarStoreContext { + state: { + activePanel: { + panel: SidebarComponent; + query?: string; + clearQuery?: () => void; + }; + selectedItemKey: string | null; //Entire chained item key eg Privacy-Sandbox#cookies#frameUrl + currentItemKey: string | null; //Last sidebar item key in selectedItemKey eg frameUrl + sidebarItems: SidebarItems; + isSidebarFocused: boolean; + }; + actions: { + setIsSidebarFocused: React.Dispatch; + updateSelectedItemKey: (key: string | null, queryString?: string) => void; + onKeyNavigation: ( + event: React.KeyboardEvent, + key: string | null + ) => void; + toggleDropdown: (action: boolean, key: string) => void; + isKeyAncestor: (key: string) => boolean; + isKeySelected: (key: string) => boolean; + }; +} + +export const initialState: SidebarStoreContext = { + state: { + activePanel: { + panel: { + Element: () => '' as any, + props: {}, + }, + query: '', + clearQuery: noop, + }, + selectedItemKey: null, + currentItemKey: null, + sidebarItems: {}, + isSidebarFocused: true, + }, + actions: { + setIsSidebarFocused: noop, + updateSelectedItemKey: noop, + onKeyNavigation: noop, + toggleDropdown: noop, + isKeyAncestor: () => false, + isKeySelected: () => false, + }, +}; + +export const SidebarContext = createContext(initialState); diff --git a/packages/design-system/src/components/sidebar/useSidebar/index.ts b/packages/design-system/src/components/sidebar/useSidebar/index.ts new file mode 100644 index 000000000..c2e8430f9 --- /dev/null +++ b/packages/design-system/src/components/sidebar/useSidebar/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './provider'; +export * from './types'; +export * from './useSidebar'; diff --git a/packages/design-system/src/components/sidebar/useSidebar/index.tsx b/packages/design-system/src/components/sidebar/useSidebar/provider.tsx similarity index 59% rename from packages/design-system/src/components/sidebar/useSidebar/index.tsx rename to packages/design-system/src/components/sidebar/useSidebar/provider.tsx index 6eece3214..5061296b8 100644 --- a/packages/design-system/src/components/sidebar/useSidebar/index.tsx +++ b/packages/design-system/src/components/sidebar/useSidebar/provider.tsx @@ -16,7 +16,13 @@ /** * External dependencies. */ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { + PropsWithChildren, + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; /** * Internal dependencies. @@ -28,59 +34,34 @@ import { findPrevItem, matchKey, } from './utils'; +import { SidebarItems, useSidebarProps } from './types'; +import { SidebarContext, SidebarStoreContext, initialState } from './context'; -export type SidebarItemValue = { - title: string; - children: SidebarItems; - popupTitle?: string; - infoIconDescription?: string; - extraInterfaceToTitle?: React.ReactNode; - dropdownOpen?: boolean; - panel?: React.ReactNode; - icon?: React.ReactNode; - selectedIcon?: React.ReactNode; - isBlurred?: boolean; -}; - -export type SidebarItems = { - [key: string]: SidebarItemValue; -}; - -export interface SidebarOutput { - activePanel: React.ReactNode; - selectedItemKey: string | null; //Entire chained item key eg Privacy-Sandbox#cookies#frameUrl - currentItemKey: string | null; //Last sidebar item key in selectedItemKey eg frameUrl - sidebarItems: SidebarItems; - isSidebarFocused: boolean; - setIsSidebarFocused: React.Dispatch; - updateSelectedItemKey: (key: string | null) => void; - onKeyNavigation: ( - event: React.KeyboardEvent, - key: string | null - ) => void; - toggleDropdown: (action: boolean, key: string) => void; - isKeyAncestor: (key: string) => boolean; - isKeySelected: (key: string) => boolean; -} - -interface useSidebarProps { - data: SidebarItems; - defaultSelectedItemKey?: string | null; -} - -const useSidebar = ({ +export const SidebarProvider = ({ data, defaultSelectedItemKey = null, -}: useSidebarProps): SidebarOutput => { + children, +}: PropsWithChildren) => { const [selectedItemKey, setSelectedItemKey] = useState(null); - const [activePanel, setActivePanel] = useState(); // TODO: Should we use React.ReactNode in state? + const [activePanel, setActivePanel] = useState< + SidebarStoreContext['state']['activePanel'] + >(initialState.state.activePanel); + const [query, setQuery] = useState(''); const [sidebarItems, setSidebarItems] = useState({}); const [isSidebarFocused, setIsSidebarFocused] = useState(true); + /** + * Update the selected item key when the defaultSelectedItemKey loads. + */ useEffect(() => { setSelectedItemKey(defaultSelectedItemKey); }, [defaultSelectedItemKey]); + /** + * Get the last sidebar item key in selectedItemKey chain. + * Eg: selectedItemKey = 'Privacy-Sandbox#cookies#frameUrl' + * currentItemKey = 'frameUrl' + */ const currentItemKey = useMemo(() => { if (!selectedItemKey) { return null; @@ -92,13 +73,25 @@ const useSidebar = ({ return keys[length - 1]; }, [selectedItemKey]); + /** + * Update the sidebar items when the sidebar data changes. + */ useEffect(() => { setSidebarItems(data); }, [data]); + /** + * Find the active panel when the selected item key changes. + */ useEffect(() => { let keyFound = false; + /** + * Find the active panel, if the selected item key is matched with the item key. + * If the item key is matched, set the active panel. + * If the item has children, recursively find the active panel. + * @param items Sidebar items. + */ const findActivePanel = (items: SidebarItems) => { Object.entries(items).forEach(([itemKey, item]) => { if (keyFound) { @@ -106,7 +99,15 @@ const useSidebar = ({ } if (matchKey(selectedItemKey || '', itemKey)) { - setActivePanel(item.panel); + if (item.panel) { + setActivePanel({ + query, + clearQuery: () => { + setTimeout(() => setQuery('')); + }, + panel: item.panel, + }); + } keyFound = true; return; @@ -121,10 +122,15 @@ const useSidebar = ({ if (selectedItemKey) { findActivePanel(sidebarItems); } - }, [selectedItemKey, sidebarItems]); + }, [query, selectedItemKey, sidebarItems]); + /** + * Update the selected item key and query string. + * @param key Selected item key. + * @param queryString Query string to pass to the new panel. + */ const updateSelectedItemKey = useCallback( - (key: string | null) => { + (key: string | null, queryString = '') => { const keyPath = createKeyPath(sidebarItems, key || ''); if (!keyPath.length) { @@ -133,10 +139,16 @@ const useSidebar = ({ } setSelectedItemKey(keyPath.join('#')); + setQuery(queryString); }, [sidebarItems] ); + /** + * Toggle the dropdown of the sidebar item. + * @param action Dropdown action. + * @param key Sidebar item key to toggle dropdown. + */ const toggleDropdown = useCallback((action: boolean, key: string) => { setSidebarItems((prev) => { const items = { ...prev }; @@ -150,6 +162,11 @@ const useSidebar = ({ }); }, []); + /** + * Handle keyboard navigation in the sidebar. + * @param event Keyboard event. + * @param key Sidebar item key to navigate. + */ const onKeyNavigation = useCallback( (event: React.KeyboardEvent, key: string | null) => { event.preventDefault(); @@ -166,12 +183,18 @@ const useSidebar = ({ return; } + /** + * Open or close the dropdown based on the navigation action. + */ if (navigationAction === 'ArrowRight') { toggleDropdown(true, key); } else if (navigationAction === 'ArrowLeft') { toggleDropdown(false, key); } + /** + * Navigate to the previous or next sidebar item based on the navigation action. + */ if (navigationAction === 'ArrowUp') { const prevItem = findPrevItem(sidebarItems, keyPath); @@ -195,6 +218,11 @@ const useSidebar = ({ [sidebarItems, toggleDropdown, updateSelectedItemKey] ); + /** + * Check if the key is an ancestor of the selected item key. + * @param key Sidebar item key to check. + * @returns boolean + */ const isKeyAncestor = useCallback( (key: string) => { if (!selectedItemKey) { @@ -209,6 +237,13 @@ const useSidebar = ({ [selectedItemKey] ); + /** + * Check if the key is present in the selected item key chain. + * Eg: selectedItemKey = 'Privacy-Sandbox#cookies#frameUrl' + * isKeySelected('frameUrl') => true + * @param key Sidebar item key to check. + * @returns boolean + */ const isKeySelected = useCallback( (key: string) => { if (!selectedItemKey) { @@ -223,19 +258,27 @@ const useSidebar = ({ [selectedItemKey] ); - return { - activePanel, - selectedItemKey, - currentItemKey, - sidebarItems, - isSidebarFocused, - setIsSidebarFocused, - updateSelectedItemKey, - onKeyNavigation, - toggleDropdown, - isKeyAncestor, - isKeySelected, - }; + return ( + + {children} + + ); }; - -export default useSidebar; diff --git a/packages/design-system/src/components/sidebar/useSidebar/types.ts b/packages/design-system/src/components/sidebar/useSidebar/types.ts new file mode 100644 index 000000000..3d0a1ab4d --- /dev/null +++ b/packages/design-system/src/components/sidebar/useSidebar/types.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type SidebarComponent = { + Element?: (props: any) => React.JSX.Element; + props?: Record; +}; + +export type SidebarItemValue = { + title: string; + children: SidebarItems; + popupTitle?: string; + infoIconDescription?: string; + extraInterfaceToTitle?: SidebarComponent; + dropdownOpen?: boolean; + panel?: SidebarComponent; + icon?: SidebarComponent; + selectedIcon?: SidebarComponent; + isBlurred?: boolean; +}; + +export type SidebarItems = { + [key: string]: SidebarItemValue; +}; + +export interface useSidebarProps { + data: SidebarItems; + defaultSelectedItemKey?: string | null; +} diff --git a/packages/design-system/src/components/sidebar/useSidebar/useSidebar.tsx b/packages/design-system/src/components/sidebar/useSidebar/useSidebar.tsx new file mode 100644 index 000000000..7602c7538 --- /dev/null +++ b/packages/design-system/src/components/sidebar/useSidebar/useSidebar.tsx @@ -0,0 +1,33 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useContextSelector } from '@ps-analysis-tool/common'; +import { SidebarContext, SidebarStoreContext } from './context'; + +export function useSidebar(): SidebarStoreContext; +export function useSidebar(selector: (state: SidebarStoreContext) => T): T; + +/** + * Hook to access the Sidebar context. + * @param selector Selector function to partially select state. + * @returns selected part of the state + */ +export function useSidebar( + selector: (state: SidebarStoreContext) => T | SidebarStoreContext = (state) => + state +) { + return useContextSelector(SidebarContext, selector); +} diff --git a/packages/design-system/src/components/sidebar/useSidebar/utils/createKeyPath.ts b/packages/design-system/src/components/sidebar/useSidebar/utils/createKeyPath.ts index 956aaaa14..461ea8346 100644 --- a/packages/design-system/src/components/sidebar/useSidebar/utils/createKeyPath.ts +++ b/packages/design-system/src/components/sidebar/useSidebar/utils/createKeyPath.ts @@ -17,9 +17,16 @@ /** * Internal dependencies */ -import { SidebarItems } from '..'; +import { SidebarItems } from '../types'; import matchKey from './matchKey'; +/** + * Create a key path to the given key in the sidebar items. + * @param items Sidebar items. + * @param key Key up to which the path should be created. + * @param keyPath Prefix key path to start with. + * @returns Key path. + */ const createKeyPath = ( items: SidebarItems, key: string, diff --git a/packages/design-system/src/components/sidebar/useSidebar/utils/findItem.ts b/packages/design-system/src/components/sidebar/useSidebar/utils/findItem.ts index e5bfd6b24..b5f32bcb1 100644 --- a/packages/design-system/src/components/sidebar/useSidebar/utils/findItem.ts +++ b/packages/design-system/src/components/sidebar/useSidebar/utils/findItem.ts @@ -17,9 +17,17 @@ /** * Internal dependencies */ -import { SidebarItemValue, SidebarItems } from '..'; +import { SidebarItemValue, SidebarItems } from '../types'; import matchKey from './matchKey'; +/** + * Find an item in the sidebar items by key. + * The SidebarItems are assumed to be a tree structure. + * Tree traversal is done in a depth-first manner to find the item. + * @param items Sidebar items. + * @param key Key to find. + * @returns Sidebar item. + */ const findItem = ( items: SidebarItems, key: string | null diff --git a/packages/design-system/src/components/sidebar/useSidebar/utils/findKeyParent.ts b/packages/design-system/src/components/sidebar/useSidebar/utils/findKeyParent.ts index 8f36bce54..93b71dd4d 100644 --- a/packages/design-system/src/components/sidebar/useSidebar/utils/findKeyParent.ts +++ b/packages/design-system/src/components/sidebar/useSidebar/utils/findKeyParent.ts @@ -14,6 +14,12 @@ * limitations under the License. */ +/** + * Find the parent key of the given key path. + * Eg if the key path is ['a', 'b', 'c'], the parent key is 'b'. + * @param keyPath Key path array. + * @returns Parent key. + */ const findKeyParent = (keyPath: string[]) => { const length = keyPath.length; diff --git a/packages/design-system/src/components/sidebar/useSidebar/utils/findNextItem.ts b/packages/design-system/src/components/sidebar/useSidebar/utils/findNextItem.ts index 4c4df6748..808e72e4b 100644 --- a/packages/design-system/src/components/sidebar/useSidebar/utils/findNextItem.ts +++ b/packages/design-system/src/components/sidebar/useSidebar/utils/findNextItem.ts @@ -17,16 +17,32 @@ /** * Internal dependencies */ -import { SidebarItemValue, SidebarItems } from '..'; +import { SidebarItemValue, SidebarItems } from '../types'; import findItem from './findItem'; import findKeyParent from './findKeyParent'; +/** + * Handle the next item on the parent from the children. + * @param currentItem Current item. + * @returns Next item key. + */ const handleNextItemOnParent = (currentItem: SidebarItemValue) => { const children = Object.keys(currentItem.children); return children.length ? children[0] : null; }; +/** + * Find the next item in the sidebar based on the key path. + * The SidebarItems are assumed to be a tree structure. + * The key path is an array of keys that represent the path to the current item. + * Tree traversal is done in a depth-first manner to find the parent of the current item. + * And then the next sibling of the current item. + * @param items Sidebar items. + * @param keyPath Key path. + * @param skipDropdown Skip dropdown if parent has only one child, so find parent's sibling. + * @returns Next item key. + */ const findNextItem = ( items: SidebarItems, keyPath: string[], diff --git a/packages/design-system/src/components/sidebar/useSidebar/utils/findPrevItem.ts b/packages/design-system/src/components/sidebar/useSidebar/utils/findPrevItem.ts index 4f0e01d49..7b6435a19 100644 --- a/packages/design-system/src/components/sidebar/useSidebar/utils/findPrevItem.ts +++ b/packages/design-system/src/components/sidebar/useSidebar/utils/findPrevItem.ts @@ -17,10 +17,20 @@ /** * Internal dependencies */ -import { SidebarItems } from '..'; +import { SidebarItems } from '../types'; import findItem from './findItem'; import findKeyParent from './findKeyParent'; +/** + * Find the previous item in the sidebar based on the key path. + * The SidebarItems are assumed to be a tree structure. + * The key path is an array of keys that represent the path to the current item. + * Tree traversal is done in a depth-first manner to find the parent of the current item. + * And then the previous sibling of the current item. + * @param items Sidebar items. + * @param keyPath Key path. + * @returns Previous item key. + */ const findPrevItem = (items: SidebarItems, keyPath: string[]) => { if (keyPath.length === 0) { return null; @@ -41,12 +51,13 @@ const findPrevItem = (items: SidebarItems, keyPath: string[]) => { return parentKey; } - const prevKey = keys[currentIndex - 1]; - const prevItem = children[prevKey]; + let prevKey = keys[currentIndex - 1]; + let prevItem = children[prevKey]; - if (prevItem?.dropdownOpen) { + while (prevItem?.dropdownOpen) { const prevItemChildren = Object.keys(prevItem.children); - return prevItemChildren[prevItemChildren.length - 1]; + prevItem = prevItem.children[prevItemChildren[prevItemChildren.length - 1]]; + prevKey = prevItemChildren[prevItemChildren.length - 1]; } return prevKey; diff --git a/packages/design-system/src/components/sidebar/useSidebar/utils/matchKey.ts b/packages/design-system/src/components/sidebar/useSidebar/utils/matchKey.ts index 53453a43f..582b50cb0 100644 --- a/packages/design-system/src/components/sidebar/useSidebar/utils/matchKey.ts +++ b/packages/design-system/src/components/sidebar/useSidebar/utils/matchKey.ts @@ -14,8 +14,12 @@ * limitations under the License. */ -/** */ - +/** + * Match toMatch key with the last key in the chained key. + * @param key chained key to match with. + * @param toMatch String to match. + * @returns Boolean. + */ const matchKey = (key: string, toMatch: string) => { const keys = key.split('#'); const length = keys.length; diff --git a/packages/design-system/src/components/sidebar/useSidebar/utils/tests/createKeyPath.ts b/packages/design-system/src/components/sidebar/useSidebar/utils/tests/createKeyPath.ts new file mode 100644 index 000000000..8e6a09212 --- /dev/null +++ b/packages/design-system/src/components/sidebar/useSidebar/utils/tests/createKeyPath.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import createKeyPath from '../createKeyPath'; + +describe('createKeyPath', () => { + it('should create a key path', () => { + const items = { + item1: { + title: 'Item 1', + children: { + item2: { + title: 'Item 2', + children: { + item3: { + title: 'Item 3', + children: { + item4: { + title: 'Item 4', + children: {}, + }, + }, + }, + }, + }, + }, + }, + }; + + let key = 'item4'; + + const result = createKeyPath(items, key); + + expect(result).toEqual(['item1', 'item2', 'item3', 'item4']); + + key = 'item5'; + + const result2 = createKeyPath(items, key); + + expect(result2).toEqual([]); + }); +}); diff --git a/packages/design-system/src/components/sidebar/useSidebar/utils/tests/findItem.ts b/packages/design-system/src/components/sidebar/useSidebar/utils/tests/findItem.ts new file mode 100644 index 000000000..99c19024e --- /dev/null +++ b/packages/design-system/src/components/sidebar/useSidebar/utils/tests/findItem.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import findItem from '../findItem'; + +describe('findItem', () => { + it('should find an item', () => { + const items = { + item1: { + title: 'Item 1', + children: { + item2: { + title: 'Item 2', + children: { + item3: { + title: 'Item 3', + children: { + item4: { + title: 'Item 4', + children: {}, + }, + }, + }, + }, + }, + }, + }, + }; + + let key = 'item4'; + + const result = findItem(items, key); + + expect(result).toEqual({ title: 'Item 4', children: {} }); + + key = 'item5'; + + const result2 = findItem(items, key); + + expect(result2).toEqual(null); + }); +}); diff --git a/packages/design-system/src/components/sidebar/useSidebar/utils/tests/findKeyParent.ts b/packages/design-system/src/components/sidebar/useSidebar/utils/tests/findKeyParent.ts new file mode 100644 index 000000000..0dbb84207 --- /dev/null +++ b/packages/design-system/src/components/sidebar/useSidebar/utils/tests/findKeyParent.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import findKeyParent from '../findKeyParent'; + +describe('findKeyPareny', () => { + it('should find the parent key', () => { + const keyPath = ['item1', 'item2', 'item3', 'item4']; + + const result = findKeyParent(keyPath); + + expect(result).toEqual('item3'); + }); + + it('should return null if there is no parent', () => { + const keyPath = ['item1']; + + const result = findKeyParent(keyPath); + + expect(result).toEqual(null); + }); +}); diff --git a/packages/design-system/src/components/sidebar/useSidebar/utils/tests/findNextItem.ts b/packages/design-system/src/components/sidebar/useSidebar/utils/tests/findNextItem.ts new file mode 100644 index 000000000..581e84657 --- /dev/null +++ b/packages/design-system/src/components/sidebar/useSidebar/utils/tests/findNextItem.ts @@ -0,0 +1,65 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SidebarItems } from '../../types'; +import findNextItem from '../findNextItem'; + +describe('findNextItem', () => { + it('should find the next item', () => { + const items: SidebarItems = { + item1: { + title: 'Item 1', + children: { + item2: { + title: 'Item 2', + children: { + item3: { + title: 'Item 3', + dropdownOpen: true, + children: { + item4: { + title: 'Item 4', + children: {}, + }, + item6: { + title: 'Item 6', + children: {}, + }, + }, + }, + }, + }, + item5: { + title: 'Item 5', + children: {}, + }, + }, + }, + }; + + const keyPath = ['item1', 'item2', 'item3', 'item4']; + + const result = findNextItem(items, keyPath); + + expect(result).toEqual('item6'); + + const keyPath2 = ['item1', 'item2', 'item3', 'item6']; + + const result2 = findNextItem(items, keyPath2); + + expect(result2).toEqual('item5'); + }); +}); diff --git a/packages/design-system/src/components/sidebar/useSidebar/utils/tests/findPrevItem.ts b/packages/design-system/src/components/sidebar/useSidebar/utils/tests/findPrevItem.ts new file mode 100644 index 000000000..fbb2bf3c1 --- /dev/null +++ b/packages/design-system/src/components/sidebar/useSidebar/utils/tests/findPrevItem.ts @@ -0,0 +1,75 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SidebarItems } from '../../types'; +import findPrevItem from '../findPrevItem'; + +describe('findPrevItem', () => { + it('should find the previous item', () => { + const items: SidebarItems = { + item1: { + title: 'Item 1', + children: { + item2: { + title: 'Item 2', + dropdownOpen: true, + children: { + item3: { + title: 'Item 3', + dropdownOpen: true, + children: { + item4: { + title: 'Item 4', + children: {}, + }, + item6: { + title: 'Item 6', + children: { + item7: { + title: 'Item 7', + children: {}, + }, + item8: { + title: 'Item 8', + children: {}, + }, + }, + }, + }, + }, + }, + }, + item5: { + title: 'Item 5', + children: {}, + }, + }, + }, + }; + + const keyPath = ['item1', 'item2', 'item3', 'item4']; + + const result = findPrevItem(items, keyPath); + + expect(result).toEqual('item3'); + + const keyPath2 = ['item1', 'item5']; + + const result2 = findPrevItem(items, keyPath2); + + expect(result2).toEqual('item6'); + }); +}); diff --git a/packages/design-system/src/components/table/components/columnMenu/columnList.tsx b/packages/design-system/src/components/table/components/columnMenu/columnList.tsx index 6bda1dc3c..111e55fce 100644 --- a/packages/design-system/src/components/table/components/columnMenu/columnList.tsx +++ b/packages/design-system/src/components/table/components/columnMenu/columnList.tsx @@ -23,38 +23,38 @@ import React, { useEffect } from 'react'; * Internal dependencies. */ import ColumnListItem from './columnListItem'; -import type { TableOutput } from '../../useTable'; +import { useTable } from '../../useTable'; interface ColumnListProps { - table: TableOutput; toggleVisibility: (key: string) => void; handleClose: () => void; } -const ColumnList = ({ - table, - toggleVisibility, - handleClose, -}: ColumnListProps) => { +const ColumnList = ({ toggleVisibility, handleClose }: ColumnListProps) => { + const { hideableColumns, isColumnHidden } = useTable( + ({ state, actions }) => ({ + hideableColumns: state.hideableColumns, + isColumnHidden: actions.isColumnHidden, + }) + ); + useEffect(() => { return () => { const visibleColumns: Record = {}; - table.hideableColumns.forEach((column) => { - visibleColumns[column.header] = table.isColumnHidden( - column.accessorKey - ); + hideableColumns.forEach((column) => { + visibleColumns[column.header] = isColumnHidden(column.accessorKey); }); }; - }, [table, table.hideableColumns]); + }, [hideableColumns, isColumnHidden]); return (