diff --git a/clients/admin-ui/global.d.ts b/clients/admin-ui/global.d.ts index f3b31e74e22..58eae84914d 100644 --- a/clients/admin-ui/global.d.ts +++ b/clients/admin-ui/global.d.ts @@ -5,6 +5,8 @@ declare module globalThis { let fidesDebugger: (...args: unknown[]) => void; } +declare module "*.ttf"; + interface Window { // Cypress is available on window when running in Cypress tests Cypress?: any; diff --git a/clients/admin-ui/jest.config.js b/clients/admin-ui/jest.config.js index 88c792d9f58..0e3b26ac36a 100644 --- a/clients/admin-ui/jest.config.js +++ b/clients/admin-ui/jest.config.js @@ -51,6 +51,7 @@ module.exports = { transformIgnorePatterns: [ "/node_modules/(?!(react-hotkeys-hook|@ant-design/x-markdown)/)", "^.+\\.module\\.(css|sass|scss)$", + "^.+.(ttf)$", ], watchPathIgnorePatterns: ["node_modules"], }; diff --git a/clients/admin-ui/next.config.js b/clients/admin-ui/next.config.js index e5376928c95..d640d32c157 100644 --- a/clients/admin-ui/next.config.js +++ b/clients/admin-ui/next.config.js @@ -21,6 +21,14 @@ const nextConfig = { resolveAlias: { antd: "antd/lib", }, + rules: { + "*.ttf": { + type: "asset", + }, + "*.woff2": { + type: "asset", + }, + }, }, webpack: (config) => { config.resolve.alias = { diff --git a/clients/admin-ui/package.json b/clients/admin-ui/package.json index 605846f15bf..d4d3dd44453 100644 --- a/clients/admin-ui/package.json +++ b/clients/admin-ui/package.json @@ -45,6 +45,7 @@ "@json-render/core": "^0.18.0", "@json-render/react": "^0.18.0", "@monaco-editor/react": "^4.6.0", + "@react-pdf/renderer": "^4.5.1", "@reduxjs/toolkit": "^2.6.0", "@tanstack/react-table": "^8.10.7", "@types/dagre": "^0.7.52", @@ -78,6 +79,7 @@ "react-hotkeys-hook": "^5.2.1", "react-phone-number-input": "^3.4.1", "react-redux": "^9.1.2", + "recharts": "^3.7.0", "redux-persist": "^6.0.0", "valibot": "^1.2.0", "yup": "^1.4.0", diff --git a/clients/admin-ui/src/features/common/utils.ts b/clients/admin-ui/src/features/common/utils.ts index a73c69c6b27..7f86ae98fc3 100644 --- a/clients/admin-ui/src/features/common/utils.ts +++ b/clients/admin-ui/src/features/common/utils.ts @@ -140,6 +140,9 @@ export const getBrandIconUrl = (domain: string, size = 24) => { return `https://cdn.brandfetch.io/${domain}/icon/theme/light/fallback/404/h/${size}/w/${size}?c=1idbRjELpikqQ1PLiqb`; }; +export const getBrandLogoUrl = (domain: string, size = 300) => + `https://cdn.brandfetch.io/domain/${domain}/type/logo.png/theme/dark/fallback/404/h/${size}/w/${size * 3}?c=1idbRjELpikqQ1PLiqb`; + export const getDomain = (urlOrDomain: string): string => { try { // Try to parse as URL first diff --git a/clients/admin-ui/src/features/data-discovery-and-detection/action-center/action-center.slice.ts b/clients/admin-ui/src/features/data-discovery-and-detection/action-center/action-center.slice.ts index 7d82c1fe55a..8b7ea151f0a 100644 --- a/clients/admin-ui/src/features/data-discovery-and-detection/action-center/action-center.slice.ts +++ b/clients/admin-ui/src/features/data-discovery-and-detection/action-center/action-center.slice.ts @@ -21,12 +21,14 @@ import { import { AggregateStatisticsResponse } from "~/types/api/models/AggregateStatisticsResponse"; import { APIMonitorType } from "~/types/api/models/APIMonitorType"; import { BaseStagedResourcesRequest } from "~/types/api/models/BaseStagedResourcesRequest"; +import { ClassificationBreakdownResponse } from "~/types/api/models/ClassificationBreakdownResponse"; import { ConditionalTotalCursorPage_DatastoreStagedResourceTreeAPIResponse_ } from "~/types/api/models/ConditionalTotalCursorPage_DatastoreStagedResourceTreeAPIResponse_"; import { DatastoreMonitorResourcesDynamicFilters } from "~/types/api/models/DatastoreMonitorResourcesDynamicFilters"; import { DatastoreStagedResourceTreeAPIResponse } from "~/types/api/models/DatastoreStagedResourceTreeAPIResponse"; import { ExecutionLogStatus } from "~/types/api/models/ExecutionLogStatus"; import { MonitorActionResponse } from "~/types/api/models/MonitorActionResponse"; import { MonitorTaskResponse } from "~/types/api/models/MonitorTaskResponse"; +import { VendorBreakdownResponse } from "~/types/api/models/VendorBreakdownResponse"; import { PaginatedResponse, PaginationQueryParams, @@ -545,7 +547,34 @@ const actionCenterApi = baseApi.injectEndpoints({ }, providesTags: ["Monitor Tasks"], }), - + getVendors: build.query< + VendorBreakdownResponse, + { + monitor_config_id?: string; + } + >({ + query: ({ ...params }) => { + return { + url: `/plus/discovery-monitor/report/vendors`, + params, + }; + }, + // providesTags: ["Monitor Statistics"], + }), + getClassifications: build.query< + ClassificationBreakdownResponse, + { + monitor_config_id?: string; + } + >({ + query: ({ ...params }) => { + return { + url: `/plus/discovery-monitor/report/classifications`, + params, + }; + }, + // providesTags: ["Monitor Statistics"], + }), getAggregateStatistics: build.query< AggregateStatisticsResponse, { @@ -680,6 +709,7 @@ export const { useLazyGetMonitorTreeQuery, useLazyGetMonitorTreeAncestorsStatusesQuery, useGetMonitorConfigQuery, + useLazyGetMonitorConfigQuery, useGetDatastoreFiltersQuery, useClassifyStagedResourcesMutation, useGetStagedResourceDetailsQuery, @@ -689,5 +719,9 @@ export const { useGetAggregateStatisticsQuery, useGetConnectionQuery, useCalcAggregateStatisticsMutation, + useGetVendorsQuery, + useLazyGetVendorsQuery, + useGetClassificationsQuery, + useLazyGetClassificationsQuery, util: actionCenterUtil, } = actionCenterApi; diff --git a/clients/admin-ui/src/features/data-discovery-and-detection/monitor-report/MonitorReport.tsx b/clients/admin-ui/src/features/data-discovery-and-detection/monitor-report/MonitorReport.tsx new file mode 100644 index 00000000000..e985129273c --- /dev/null +++ b/clients/admin-ui/src/features/data-discovery-and-detection/monitor-report/MonitorReport.tsx @@ -0,0 +1,129 @@ +import { Page, StyleSheet, Text, View } from "@react-pdf/renderer"; +import { palette } from "fidesui/src/palette/palette"; + +import { PdfFooter } from "~/features/pdf/Footer"; +import { PdfHeader } from "~/features/pdf/Header"; +import { HeaderStyles } from "~/features/pdf/PdfStyles"; +import { TableOfContentsPdf } from "~/features/pdf/TableOfContents"; +import { TitlePage } from "~/features/pdf/TitlePage"; +import { PdfWrapper } from "~/features/pdf/Wrapper"; +import { AggregateStatisticsResponse } from "~/types/api"; +import { ClassificationBreakdownItem } from "~/types/api/models/ClassificationBreakdownItem"; +import { VendorBreakdownItem } from "~/types/api/models/VendorBreakdownItem"; + +import { MonitorReportDataElements } from "./sections/MonitorReportDataElements"; +import { MonitorReportSummary } from "./sections/MonitorReportSummary"; +import { MonitorReportsThirdParties } from "./sections/MonitorReportThirdParties"; + +const styles = StyleSheet.create({ + page: { + paddingHorizontal: 40, + paddingTop: 40, + flexDirection: "column", + backgroundColor: palette.FIDESUI_BG_WHITE, + justifyContent: "space-evenly", + }, + section: { + display: "flex", + flexDirection: "column", + gap: 10, + fontFamily: "BasierSquare", + fontSize: 12, + marginVertical: 10, + paddingVertical: 10, + flexGrow: 1, + }, +}); + +const SECTIONS = [ + { + title: "Summary", + description: + "Dashboard of detected systems, scripts, cookies, and consent issues.", + }, + { + title: "Third Parties", + description: "All vendors observed, categorized with data they receive.", + }, + { + title: "Data Elements", + description: + "Fides data-use taxonomy labels and the vendors receiving each.", + }, +] as const; + +export const MonitorReport = ({ + author, + classifications, + classificationsChart, + imageSrc, + location, + monitorTitle, + stats, + vendors, + vendorsChart, +}: { + author: string; + classifications: Array; + classificationsChart: string; + imageSrc: string; + location: string; + monitorTitle: string; + stats: AggregateStatisticsResponse; + vendors: Array; + vendorsChart: string; +}) => ( + + + + + + Contents + Report details + + Table of contents + + + + + + {SECTIONS.map(({ title }, sectionIndex) => ( + + + + {`Chapter ${sectionIndex + 1}`} + {title} + {title === "Summary" && ( + + )} + {title === "Data Elements" && ( + + )} + {title === "Third Parties" && ( + + )} + + + + ))} + +); diff --git a/clients/admin-ui/src/features/data-discovery-and-detection/monitor-report/MonitorReportDownload.tsx b/clients/admin-ui/src/features/data-discovery-and-detection/monitor-report/MonitorReportDownload.tsx new file mode 100644 index 00000000000..c92003b1cbb --- /dev/null +++ b/clients/admin-ui/src/features/data-discovery-and-detection/monitor-report/MonitorReportDownload.tsx @@ -0,0 +1,24 @@ +import { Button, Icons, Tooltip } from "fidesui"; +import { useRouter } from "next/router"; + +import { useMonitorReportDownload } from "./useMonitorReportDownload"; + +const MonitorReportDownload = () => { + const router = useRouter(); + const monitorId = decodeURIComponent(router.query.monitorId as string); + const { isGenerating, generate } = useMonitorReportDownload(monitorId); + + return ( + +