Skip to content

Commit

Permalink
Feature: Export report in an HTML file (#545)
Browse files Browse the repository at this point in the history
* feat: add summary export components

* ref:report generation

* style-fix: redesign download report button

* fix:no of frames showing up

* Minor refactoring

* Minor improvements

* ref: remove devServer config for report

* ref: add types

* ref: refactor report app assembly

* feat: add library detection section in report

* ref: move download button

* feat:disable download button until library detection is done

* feat: add disable reason in download report button

* style-fix: update font-sized

* feat: name report file based on top level url

* ref: move download button to menubar

* style-fix: scale down download item

* Fix infinite loading issue when a new tab is opened
The issue originally started in PR#562 when we were trying to fix the case when the user visits a 404 page and the library detection section goes into infinite loop

* ref:refactor report view

* ref: add unit test for report view

* ref: add unit test for report object generation

* ref: move dependencies and return null with invalid data

* ref: move dependencies and return null with invalid data

---------

Co-authored-by: sayedtaqui <sayedwp@gmail.com>
  • Loading branch information
ayushnirwal and mohdsayed authored Apr 9, 2024
1 parent a83a404 commit f0a113e
Show file tree
Hide file tree
Showing 30 changed files with 28,750 additions and 395 deletions.
16,813 changes: 16,474 additions & 339 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"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",
Expand Down
15 changes: 15 additions & 0 deletions packages/common/src/cookies.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,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[];
};
3 changes: 3 additions & 0 deletions packages/design-system/src/components/button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import React from 'react';

interface ButtonProps {
text: string | React.ReactNode;
title?: string;
name?: string;
onClick?: () => void;
loading?: boolean;
Expand All @@ -30,6 +31,7 @@ interface ButtonProps {
disabled?: boolean;
}
const Button = ({
title = '',
text,
name = 'button',
onClick = undefined,
Expand All @@ -40,6 +42,7 @@ const Button = ({
}: ButtonProps) => {
return (
<button
title={title}
data-test-id="button"
type={type}
name={name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,9 @@
*/
import React from 'react';
import { CirclePieChart } from '@ps-analysis-tool/design-system';
import { DataMapping } from '@ps-analysis-tool/common';
import classnames from 'classnames';

export interface DataMapping {
title: string;
count: number;
data: {
count: number;
color: string;
}[];
onClick?: () => void;
}

interface LandingHeaderProps {
dataMapping?: DataMapping[];
}
Expand All @@ -45,32 +36,34 @@ const LandingHeader = ({ dataMapping = [] }: LandingHeaderProps) => {
<div className="lg:max-w-[729px] flex gap-9 px-4">
{dataMapping.map((circleData, index) => {
return (
<button
key={index}
className={classnames('group text-center w-20 p-2 h-full', {
'active:opacity-50 hover:scale-95 transition-all duration-300 ease-in-out cursor-pointer ':
circleData.onClick,
'cursor-default': !circleData.onClick,
})}
onClick={() => {
circleData.onClick?.();
}}
>
<CirclePieChart
title={circleData.title}
centerCount={circleData.count}
data={circleData.data}
infoIconClassName="absolute -right-3"
centerTitleExtraClasses={classnames({
'group-hover:scale-125 transition-all duration-300 ease-in-out':
circleData.onClick,
})}
pieChartExtraClasses={classnames({
'group-hover:scale-[1.15] transition-all duration-200 ease-in-out group-hover:bg-[#f3f3f3] group-hover:dark:bg-[#191919] rounded-full':
<div key={index} className="text-center w-16 h-fit">
<button
key={index}
className={classnames('group text-center w-20 p-2 h-full', {
'active:opacity-50 hover:scale-95 transition-all duration-300 ease-in-out cursor-pointer ':
circleData.onClick,
'cursor-default': !circleData.onClick,
})}
/>
</button>
onClick={() => {
circleData.onClick?.();
}}
>
<CirclePieChart
title={circleData.title}
centerCount={circleData.count}
data={circleData.data}
infoIconClassName="absolute -right-3"
centerTitleExtraClasses={classnames({
'group-hover:scale-125 transition-all duration-300 ease-in-out':
circleData.onClick,
})}
pieChartExtraClasses={classnames({
'group-hover:scale-[1.15] transition-all duration-200 ease-in-out group-hover:bg-[#f3f3f3] group-hover:dark:bg-[#191919] rounded-full':
circleData.onClick,
})}
/>
</button>
</div>
);
})}
</div>
Expand Down
28 changes: 28 additions & 0 deletions packages/design-system/src/components/menuBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/
import React, { useEffect, useState } from 'react';
import classnames from 'classnames';
import { Export } from '@ps-analysis-tool/design-system';

/**
* Internal dependencies.
Expand All @@ -31,12 +32,16 @@ export type MenuData = Array<{
}>;

interface MenuBarProps {
disableReportDownload: boolean;
downloadReport?: () => void;
menuData: MenuData;
extraClasses?: string;
scrollContainerId: string;
}

const MenuBar = ({
disableReportDownload = true,
downloadReport,
menuData,
extraClasses,
scrollContainerId,
Expand Down Expand Up @@ -103,6 +108,29 @@ const MenuBar = ({
extraClasses ? extraClasses : 'top-4'
)}
>
{downloadReport && (
<button
disabled={disableReportDownload}
className={classnames(
'flex items-center relative justify-center w-5 h-5 p-1 right-1 rounded-full cursor-pointer transition-all ease-in-out group',
{
'bg-baby-blue-eyes': disableReportDownload,
'bg-ultramarine-blue': !disableReportDownload,
}
)}
onClick={() => {
downloadReport();
}}
>
<div className="absolute flex items-center justify-center right-6 w-max px-3 py-1 rounded invisible text-sm text-white bg-ultramarine-blue group-hover:visible transition-all ease-in-out">
{disableReportDownload
? 'Wait for library detection'
: 'Download Report'}
<div className="absolute w-2 h-2 bg-ultramarine-blue top-1/3 -right-1 transform rotate-45" />
</div>
<Export className="text-white scale-75" />
</button>
)}
{menuData.map((item, index) => (
<div
key={index}
Expand Down
3 changes: 2 additions & 1 deletion packages/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
},
"devDependencies": {
"@types/react-copy-to-clipboard": "^5.0.4",
"devtools-protocol": "^0.0.1236148"
"devtools-protocol": "^0.0.1236148",
"html-inline-script-webpack-plugin": "^3.2.1"
}
}
2 changes: 1 addition & 1 deletion packages/extension/src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
},
"web_accessible_resources": [
{
"resources": ["assets/data/*.json", "data/*json"],
"resources": ["assets/data/*.json", "data/*json", "report/index.html"],
"matches": ["*://*/*"]
}
]
Expand Down
66 changes: 66 additions & 0 deletions packages/extension/src/utils/downloadReport.ts
Original file line number Diff line number Diff line change
@@ -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.
*/
/**
* External dependencies.
*/
import type {
LibraryData,
TabCookies,
TabFrames,
} from '@ps-analysis-tool/common';
import { saveAs } from 'file-saver';
/**
* Internal dependencies.
*/
import generateReportObject from './generateReportObject';

/**
* Utility function to download report.
* @param url Top level URL.
* @param tabCookies Tab cookies.
* @param tabFrames Tab frames.
* @param libraryMatches
*/
export default async function downloadReport(
url: string,
tabCookies: TabCookies,
tabFrames: TabFrames,
libraryMatches: LibraryData
) {
const htmlText = await (await fetch('../report/index.html')).text();
const parser = new DOMParser();
const reportDom = parser.parseFromString(htmlText, 'text/html');

// Injections
const script = reportDom.createElement('script');

const reportData = generateReportObject(
tabCookies,
tabFrames,
libraryMatches
);

const code = `window.PSAT_DATA = ${JSON.stringify(reportData)}`;

script.text = code;
reportDom.head.appendChild(script);

const injectedHtmlText = `<head>${reportDom.head.innerHTML}<head><body>${reportDom.body.innerHTML}</body>`;
const html = new Blob([injectedHtmlText]);
const hostname = new URL(url).hostname;

saveAs(html, `${hostname.replace('.', '-')}-report.html`);
}
87 changes: 87 additions & 0 deletions packages/extension/src/utils/generateReportObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* 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 type {
LibraryData,
TabCookies,
TabFrames,
} from '@ps-analysis-tool/common';

import {
prepareCookieStatsComponents,
prepareCookiesCount,
prepareFrameStatsComponent,
type DataMapping,
} from '@ps-analysis-tool/design-system';

/**
* Utility function to generate report object.
* @param url Top level URL.
* @param tabCookies Tab cookies.
* @param tabFrames Tab frames.
* @param libraryMatches
*/
export default function generateReportObject(
tabCookies: TabCookies,
tabFrames: TabFrames,
libraryMatches: LibraryData
) {
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,
},
];

return {
cookieClassificationDataMapping,
tabCookies,
cookiesStatsComponents,
tabFrames,
showInfoIcon: true,
showHorizontalMatrix: false,
blockedCookieDataMapping,
showBlockedInfoIcon: true,
frameStateCreator,
libraryMatches,
};
}
Loading

0 comments on commit f0a113e

Please sign in to comment.