diff --git a/changelog/7751-aggregate-statistics.yaml b/changelog/7751-aggregate-statistics.yaml new file mode 100644 index 0000000000..4b22a9597e --- /dev/null +++ b/changelog/7751-aggregate-statistics.yaml @@ -0,0 +1,4 @@ +type: Added # One of: Added, Changed, Developer Experience, Deprecated, Docs, Fixed, Removed, Security +description: Adding aggregate statistics widgets to action center +pr: 7751 # PR number +labels: [] # Optional: ["high-risk", "db-migration"] diff --git a/clients/admin-ui/src/features/common/utils.ts b/clients/admin-ui/src/features/common/utils.ts index 3c9bd143cc..db9890a2c4 100644 --- a/clients/admin-ui/src/features/common/utils.ts +++ b/clients/admin-ui/src/features/common/utils.ts @@ -279,20 +279,6 @@ export const truncateUrl = (url: string, limit: number): string => { } }; -/** - * Formats a number with a suffix for large numbers (K, M, etc.) - * @param num - The number to format - * @param digits - The number of digits to round to (default is 0) - * @returns The formatted number as a string - * - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat - * - * @example - * nFormatter(1111); // returns "1K" - * nFormatter(1111, 0); // returns "1K" - * nFormatter(1111, 1); // returns "1.1K" - * nFormatter(1111, 2); // returns "1.11K" - */ /** * Converts a snake_case string into a human-readable title. * Known acronyms are uppercased; other words are title-cased. @@ -310,6 +296,20 @@ export const snakeCaseToTitleCase = ( ) .join(" "); +/** + * Formats a number with a suffix for large numbers (K, M, etc.) + * @param num - The number to format + * @param digits - The number of digits to round to (default is 0) + * @returns The formatted number as a string + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat + * + * @example + * nFormatter(1111); // returns "1K" + * nFormatter(1111, 0); // returns "1K" + * nFormatter(1111, 1); // returns "1.1K" + * nFormatter(1111, 2); // returns "1.11K" + */ export const nFormatter = (num: number = 0, digits: number = 0) => Intl.NumberFormat("en", { notation: "compact", diff --git a/clients/admin-ui/src/features/data-discovery-and-detection/action-center/ActionCenterLayout.tsx b/clients/admin-ui/src/features/data-discovery-and-detection/action-center/ActionCenterLayout.tsx index 1585743274..ab3f85cd4d 100644 --- a/clients/admin-ui/src/features/data-discovery-and-detection/action-center/ActionCenterLayout.tsx +++ b/clients/admin-ui/src/features/data-discovery-and-detection/action-center/ActionCenterLayout.tsx @@ -1,3 +1,4 @@ +import classNames from "classnames"; import { Badge, BadgeProps, @@ -14,10 +15,17 @@ import { PropsWithChildren } from "react"; import FixedLayout from "~/features/common/FixedLayout"; import { ACTION_CENTER_ROUTE } from "~/features/common/nav/routes"; import PageHeader from "~/features/common/PageHeader"; +import { pluralize } from "~/features/common/utils"; import useActionCenterNavigation, { ActionCenterRoute, ActionCenterRouteConfig, } from "~/features/data-discovery-and-detection/action-center/hooks/useActionCenterNavigation"; +import { AggregateStatisticsResponse } from "~/types/api/models/AggregateStatisticsResponse"; +import { APIMonitorType } from "~/types/api/models/APIMonitorType"; + +import { useGetAggretateStatisticsQuery } from "./action-center.slice"; +import { MONITOR_UPDATE_LABELS } from "./constants"; +import { MonitorStatCard, MonitorStatCardProps } from "./MonitorStatCard"; export interface ActionCenterLayoutProps { monitorId?: string; @@ -28,6 +36,88 @@ export interface ActionCenterLayoutProps { }; } +const getMonitorUpdateName = (key: string, count: number) => { + const names = Object.entries(MONITOR_UPDATE_LABELS).find( + ([k]) => key === k, + )?.[1]; + + if (!names) { + return key; + } + // names is [singular, plural] + return pluralize(count, ...names); +}; + +const MONITOR_TYPE_TO_LABEL: Record = { + datastore: "Data stores", + infrastructure: "Infrastructure", + website: "Web monitors", +}; + +const MONITOR_TYPE_TO_PRIMARY_STATISTIC: Record = { + datastore: "Resources approved", + infrastructure: "Total systems", + website: "Resources approved", +}; + +const MONITOR_TYPE_TO_NUMERIC_STATISTIC: Record< + APIMonitorType, + keyof AggregateStatisticsResponse +> = { + datastore: "status_counts", + infrastructure: "vendor_counts", + website: "status_counts", +}; + +const MONITOR_TYPE_TO_PERCENT_STATISTIC_KEY: Record< + APIMonitorType, + keyof NonNullable +> = { + datastore: "data_categories", + infrastructure: "data_uses", + website: "data_uses", +}; + +const MONITOR_TYPE_TO_PERECENT_STATISTIC_LABEL: Record = + { + datastore: "Data categories", + infrastructure: "Data uses", + website: "Categories of consent", + }; + +export const transformStatisticsResponseToCardProps = ( + response: AggregateStatisticsResponse, +): MonitorStatCardProps => ({ + title: MONITOR_TYPE_TO_LABEL[response.monitor_type], + subtitle: `${response?.total_monitors} ${pluralize(response?.total_monitors ?? 0, "monitor", "monitors")}`, + primaryStat: { + label: MONITOR_TYPE_TO_PRIMARY_STATISTIC[response.monitor_type], + denominator: response?.approval_progress?.total, + numerator: response?.approval_progress?.approved, + percent: response?.approval_progress?.percentage, + }, + numericStats: { + label: "Current status", + data: Object.entries( + response?.[MONITOR_TYPE_TO_NUMERIC_STATISTIC[response.monitor_type]] ?? + [], + ).flatMap(([label, value]) => + value + ? [{ label: getMonitorUpdateName(label, value), count: value }] + : [], + ), + }, + percentageStats: { + label: MONITOR_TYPE_TO_PERECENT_STATISTIC_LABEL[response.monitor_type], + data: ( + response?.top_classifications?.[ + MONITOR_TYPE_TO_PERCENT_STATISTIC_KEY[response.monitor_type] + ] ?? [] + ).flatMap(({ name, percentage }) => ({ label: name, value: percentage })), + }, + lastUpdated: response?.last_updated ?? undefined, +}); + const ActionCenterLayout = ({ children, monitorId, @@ -39,6 +129,26 @@ const ActionCenterLayout = ({ activeItem, setActiveItem, } = useActionCenterNavigation(routeConfig); + const { data: websiteStatistics } = useGetAggretateStatisticsQuery( + { + monitor_type: "website", + monitor_config_id: monitorId, + }, + { refetchOnMountOrArgChange: true }, + ); + + const { data: datastoreStatistics } = useGetAggretateStatisticsQuery( + { + monitor_type: "datastore", + monitor_config_id: monitorId, + }, + { refetchOnMountOrArgChange: true }, + ); + + const { data: infrastructureStatistics } = useGetAggretateStatisticsQuery({ + monitor_type: "infrastructure", + monitor_config_id: monitorId, + }); return ( +
+ {[infrastructureStatistics, datastoreStatistics, websiteStatistics].map( + (response) => + response && (response.total_monitors ?? 0) > 0 ? ( + + ) : null, + )} +
} - iconPosition="end" + iconPlacement="end" onClick={() => setIsConfidenceRowExpanded(!isConfidenceRowExpanded) } diff --git a/clients/admin-ui/src/features/data-discovery-and-detection/action-center/MonitorResultDescription.tsx b/clients/admin-ui/src/features/data-discovery-and-detection/action-center/MonitorResultDescription.tsx index 64e88aea3f..4e4775cb65 100644 --- a/clients/admin-ui/src/features/data-discovery-and-detection/action-center/MonitorResultDescription.tsx +++ b/clients/admin-ui/src/features/data-discovery-and-detection/action-center/MonitorResultDescription.tsx @@ -3,7 +3,7 @@ import { useMemo } from "react"; import { nFormatter, pluralize } from "~/features/common/utils"; import { - MONITOR_UPDATE_NAMES, + MONITOR_UPDATE_LABELS, MONITOR_UPDATE_ORDER, MONITOR_UPDATES_TO_IGNORE, } from "./constants"; @@ -12,12 +12,15 @@ import { MonitorUpdates } from "./types"; type MonitorUpdateKey = keyof MonitorUpdates; const getMonitorUpdateName = (key: string, count: number) => { - const names = MONITOR_UPDATE_NAMES.get(key as MonitorUpdateKey); + const names = Object.entries(MONITOR_UPDATE_LABELS).find( + ([k]) => key === k, + )?.[1]; + if (!names) { return key; } // names is [singular, plural] - return pluralize(count, names[0], names[1]); + return pluralize(count, ...names); }; export const MonitorResultDescription = ({ diff --git a/clients/admin-ui/src/features/data-discovery-and-detection/action-center/MonitorStatCard.tsx b/clients/admin-ui/src/features/data-discovery-and-detection/action-center/MonitorStatCard.tsx new file mode 100644 index 0000000000..51d3aad932 --- /dev/null +++ b/clients/admin-ui/src/features/data-discovery-and-detection/action-center/MonitorStatCard.tsx @@ -0,0 +1,256 @@ +import classNames from "classnames"; +import { + Card, + Descriptions, + Divider, + DonutChart, + Flex, + Paragraph, + TagList, + Text, + Title, +} from "fidesui"; +import { PropsWithChildren } from "react"; + +import { useRelativeTime } from "~/features/common/hooks/useRelativeTime"; +import { nFormatter } from "~/features/common/utils"; + +type NumericStat = { + label: string; + count: number; +}; + +type PercentStat = { + label: string; + value: number; +}; + +export interface MonitorStatCardProps { + title: string; + subtitle: string; + primaryStat: { + label: string; + percent?: number; + numerator?: number; + denominator?: number; + }; + numericStats?: { + data: NumericStat[]; + label: string; + }; + percentageStats?: { + data: PercentStat[]; + label: string; + }; + lastUpdated?: string; + compact?: boolean; +} + +const getProgressColor = (percent: number) => { + switch (true) { + case percent > 80: + return "colorSuccess"; + case percent > 60: + return "colorWarning"; + default: + return "colorErrorTextHover"; + } +}; + +export const MonitorStatCard = ({ + title, + subtitle, + primaryStat, + numericStats, + percentageStats, + lastUpdated, + compact, +}: PropsWithChildren) => { + const relativeTime = useRelativeTime( + lastUpdated ? new Date(lastUpdated) : new Date(), + ); + + return ( + + {title} + + {subtitle} + + + ) + } + > +
+ +
+ {`${nFormatter(primaryStat.percent)}%`} + } + variant={compact ? "thin" : "default"} + fit={compact ? "fill" : "contain"} + segments={[ + { + color: getProgressColor(primaryStat.percent ?? 0), + value: primaryStat.percent ?? 0, + }, + { + color: "colorPrimaryBg", + value: 100 - (primaryStat.percent ?? 0), + }, + ]} + /> +
+ + + {primaryStat.label} + + + {nFormatter(primaryStat.numerator)} of{" "} + {nFormatter(primaryStat.denominator)} + + +
+
+ {numericStats && ( + <> + {compact && ( + + )} +
+ + {numericStats.label} + + + {numericStats.data?.map(({ label, count }) => ( + + {nFormatter(count)} + + ))} + + ), + }, + }} + > + {numericStats.data.length > 0 + ? numericStats.data + ?.map( + ({ label, count }) => `${nFormatter(count)} ${label}`, + ) + .join(", ") + : "None"} + +
+ + )} + {percentageStats && ( + <> + +
+ + {percentageStats.label} + + {percentageStats.data.length > 0 ? ( + ({ + label: {`${nFormatter(value)}% ${label}`}, + value: `${value}%`, + }))} + /> + ) : ( +
None
+ )} +
+ + )} + + <> + Updated: {relativeTime} + { + //
+
+
+ ); +}; 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 1fc2594078..5fecbcde24 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 @@ -17,6 +17,7 @@ import { StagedResourceAPIResponse, WebsiteMonitorResourcesFilters, } from "~/types/api"; +import { AggregateStatisticsResponse } from "~/types/api/models/AggregateStatisticsResponse"; import { BaseStagedResourcesRequest } from "~/types/api/models/BaseStagedResourcesRequest"; import { ClassifyResourcesResponse } from "~/types/api/models/ClassifyResourcesResponse"; import { CursorPage_DatastoreStagedResourceTreeAPIResponse_ } from "~/types/api/models/CursorPage_DatastoreStagedResourceTreeAPIResponse_"; @@ -130,6 +131,7 @@ const actionCenterApi = baseApi.injectEndpoints({ staged_resource_urn?: string; include_descendant_details?: boolean; diff_status?: DiffStatus[]; + child_staged_resource_urns?: string[]; } >({ query: ({ @@ -139,8 +141,12 @@ const actionCenterApi = baseApi.injectEndpoints({ staged_resource_urn, include_descendant_details, diff_status, + child_staged_resource_urns, }) => { - const urlParams = buildArrayQueryParams({ diff_status }); + const urlParams = buildArrayQueryParams({ + diff_status, + child_staged_resource_urns, + }); return { url: `/plus/discovery-monitor/${monitor_config_id}/tree?${urlParams}`, @@ -524,6 +530,23 @@ const actionCenterApi = baseApi.injectEndpoints({ }, providesTags: ["Monitor Tasks"], }), + + getAggretateStatistics: build.query< + AggregateStatisticsResponse, + { + monitor_type?: "website" | "datastore" | "infrastructure"; + monitor_config_id?: string; + } + >({ + query: ({ monitor_type, ...params }) => { + return { + url: `/plus/discovery-monitor/aggregate-results/summary/${monitor_type}`, + params, + }; + }, + providesTags: ["Monitor Tasks"], + }), + retryMonitorTask: build.mutation< { id: string; @@ -616,4 +639,6 @@ export const { useGetStagedResourceDetailsQuery, useLazyGetStagedResourceDetailsQuery, usePromoteRemovalStagedResourcesMutation, + useLazyGetAggretateStatisticsQuery, + useGetAggretateStatisticsQuery, } = actionCenterApi; diff --git a/clients/admin-ui/src/features/data-discovery-and-detection/action-center/constants.ts b/clients/admin-ui/src/features/data-discovery-and-detection/action-center/constants.ts index 012ab4a456..39062f9365 100644 --- a/clients/admin-ui/src/features/data-discovery-and-detection/action-center/constants.ts +++ b/clients/admin-ui/src/features/data-discovery-and-detection/action-center/constants.ts @@ -7,6 +7,7 @@ import { WebMonitorUpdates, } from "~/types/api"; import { InfrastructureMonitorUpdates } from "~/types/api/models/InfrastructureMonitorUpdates"; +import { StatusCounts } from "~/types/api/models/StatusCounts"; import { ActionCenterTabHash } from "./hooks/useActionCenterTabs"; @@ -77,28 +78,34 @@ export const MONITOR_UPDATES_TO_IGNORE = [ | keyof WebMonitorUpdates )[]; -export const MONITOR_UPDATE_NAMES = new Map< +export const MONITOR_UPDATE_LABELS: Record< | keyof WebMonitorUpdates | keyof Omit< DatastoreMonitorUpdates, (typeof MONITOR_UPDATES_TO_IGNORE)[number] > - | keyof InfrastructureMonitorUpdates, + | keyof InfrastructureMonitorUpdates + | keyof StatusCounts, [string, string] ->([ - ["cookie", ["Cookie", "Cookies"]], - ["browser_request", ["Browser request", "Browser requests"]], - ["image", ["Image", "Images"]], - ["iframe", ["iFrame", "iFrames"]], - ["javascript_tag", ["JavaScript tag", "JavaScript tags"]], - ["unlabeled", ["Unlabeled", "Unlabeled"]], - ["in_review", ["Classified", "Classified"]], - ["classifying", ["Classifying", "Classifying"]], - ["removals", ["Removal", "Removals"]], - ["reviewed", ["Reviewed", "Reviewed"]], - ["known", ["Known System", "Known Systems"]], - ["unknown", ["Unknown System", "Unknown Systems"]], -]); +> = { + addition: ["Added", "Added"], + classified: ["Classified", "Classified"], + removal: ["Removal", "Removals"], + monitored: ["Monitored", "Monitored"], + approved: ["Approved", "Approved"], + cookie: ["Cookie", "Cookies"], + browser_request: ["Browser request", "Browser requests"], + image: ["Image", "Images"], + iframe: ["iFrame", "iFrames"], + javascript_tag: ["JavaScript tag", "JavaScript tags"], + unlabeled: ["Unlabeled", "Unlabeled"], + in_review: ["Classified", "Classified"], + classifying: ["Classifying", "Classifying"], + removals: ["Removal", "Removals"], + reviewed: ["Reviewed", "Reviewed"], + known: ["Known system", "Known systems"], + unknown: ["Unknown system", "Unknown systems"], +}; export const MONITOR_UPDATE_ORDER = [ "cookie", diff --git a/clients/admin-ui/src/types/api/models/APIMonitorType.ts b/clients/admin-ui/src/types/api/models/APIMonitorType.ts new file mode 100644 index 0000000000..bd4baadb2e --- /dev/null +++ b/clients/admin-ui/src/types/api/models/APIMonitorType.ts @@ -0,0 +1,12 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Monitor types as used by the API + */ +export enum APIMonitorType { + WEBSITE = "website", + DATASTORE = "datastore", + INFRASTRUCTURE = "infrastructure", +} diff --git a/clients/admin-ui/src/types/api/models/AggregateStatisticsResponse.ts b/clients/admin-ui/src/types/api/models/AggregateStatisticsResponse.ts new file mode 100644 index 0000000000..7b403a30e3 --- /dev/null +++ b/clients/admin-ui/src/types/api/models/AggregateStatisticsResponse.ts @@ -0,0 +1,25 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { APIMonitorType } from "./APIMonitorType"; +import type { ApprovalProgress } from "./ApprovalProgress"; +import type { StatusCounts } from "./StatusCounts"; +import type { TopClassifications } from "./TopClassifications"; +import type { VendorCounts } from "./VendorCounts"; +import type { WebResourceTypeCounts } from "./WebResourceTypeCounts"; + +/** + * Cross-monitor aggregate statistics. Same shape for all monitor types. + */ +export type AggregateStatisticsResponse = { + monitor_type: APIMonitorType; + last_updated?: string | null; + total_monitors?: number; + total_resources?: number; + status_counts?: StatusCounts; + approval_progress?: ApprovalProgress; + top_classifications?: TopClassifications; + vendor_counts?: VendorCounts | null; + resource_type_counts?: WebResourceTypeCounts | null; +}; diff --git a/clients/admin-ui/src/types/api/models/ApprovalProgress.ts b/clients/admin-ui/src/types/api/models/ApprovalProgress.ts new file mode 100644 index 0000000000..bae75eaa76 --- /dev/null +++ b/clients/admin-ui/src/types/api/models/ApprovalProgress.ts @@ -0,0 +1,12 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Approval progress tracking for monitors. + */ +export type ApprovalProgress = { + approved?: number; + total?: number; + percentage?: number; +}; diff --git a/clients/admin-ui/src/types/api/models/BenchmarkStatus.ts b/clients/admin-ui/src/types/api/models/BenchmarkStatus.ts new file mode 100644 index 0000000000..0eb2bac466 --- /dev/null +++ b/clients/admin-ui/src/types/api/models/BenchmarkStatus.ts @@ -0,0 +1,12 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Status values for classification benchmarks. + */ +export enum BenchmarkStatus { + COMPLETED = "completed", + FAILED = "failed", + PENDING = "pending", +} diff --git a/clients/admin-ui/src/types/api/models/BreakdownItem.ts b/clients/admin-ui/src/types/api/models/BreakdownItem.ts new file mode 100644 index 0000000000..100c0ade1f --- /dev/null +++ b/clients/admin-ui/src/types/api/models/BreakdownItem.ts @@ -0,0 +1,12 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * A single item in a category/data-use/consent breakdown. + */ +export type BreakdownItem = { + name: string; + count: number; + percentage: number; +}; diff --git a/clients/admin-ui/src/types/api/models/DatastoreMonitorUpdates.ts b/clients/admin-ui/src/types/api/models/DatastoreMonitorUpdates.ts index f58d9886ee..0fc9142073 100644 --- a/clients/admin-ui/src/types/api/models/DatastoreMonitorUpdates.ts +++ b/clients/admin-ui/src/types/api/models/DatastoreMonitorUpdates.ts @@ -14,4 +14,5 @@ export type DatastoreMonitorUpdates = { classified_low_confidence: number | null; classified_medium_confidence: number | null; classified_high_confidence: number | null; + approved: number | null; }; diff --git a/clients/admin-ui/src/types/api/models/ExtendedDatastoreMonitorUpdates.ts b/clients/admin-ui/src/types/api/models/ExtendedDatastoreMonitorUpdates.ts new file mode 100644 index 0000000000..78a1f1f3aa --- /dev/null +++ b/clients/admin-ui/src/types/api/models/ExtendedDatastoreMonitorUpdates.ts @@ -0,0 +1,18 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Extends DatastoreMonitorUpdates with approved count for cross-monitor views. + */ +export type ExtendedDatastoreMonitorUpdates = { + unlabeled: number; + in_review: number; + classifying: number; + removals: number; + reviewed: number; + classified_low_confidence: number | null; + classified_medium_confidence: number | null; + classified_high_confidence: number | null; + approved: number; +}; diff --git a/clients/admin-ui/src/types/api/models/StatusCounts.ts b/clients/admin-ui/src/types/api/models/StatusCounts.ts new file mode 100644 index 0000000000..0649497c98 --- /dev/null +++ b/clients/admin-ui/src/types/api/models/StatusCounts.ts @@ -0,0 +1,15 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Counts of resources by diff_status group. Consistent across all monitor types. + */ +export type StatusCounts = { + addition?: number; + classifying?: number; + classified?: number; + reviewed?: number; + monitored?: number; + removal?: number; +}; diff --git a/clients/admin-ui/src/types/api/models/SystemOverview.ts b/clients/admin-ui/src/types/api/models/SystemOverview.ts new file mode 100644 index 0000000000..3a3f810805 --- /dev/null +++ b/clients/admin-ui/src/types/api/models/SystemOverview.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * High-level system counts for infrastructure monitors. + */ +export type SystemOverview = { + total?: number; + approved?: number; + needs_attention?: number; + coverage_percentage?: number; +}; diff --git a/clients/admin-ui/src/types/api/models/TopClassifications.ts b/clients/admin-ui/src/types/api/models/TopClassifications.ts new file mode 100644 index 0000000000..0e3fda172b --- /dev/null +++ b/clients/admin-ui/src/types/api/models/TopClassifications.ts @@ -0,0 +1,16 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { BreakdownItem } from "./BreakdownItem"; + +/** + * Top N breakdowns of data categories and/or data uses. + * + * Both fields are nullable — populate whichever applies to the monitor type. + * In the future, a single monitor type may populate both. + */ +export type TopClassifications = { + data_categories?: Array | null; + data_uses?: Array | null; +}; diff --git a/clients/admin-ui/src/types/api/models/VendorCounts.ts b/clients/admin-ui/src/types/api/models/VendorCounts.ts new file mode 100644 index 0000000000..bab879952f --- /dev/null +++ b/clients/admin-ui/src/types/api/models/VendorCounts.ts @@ -0,0 +1,11 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Vendor identification breakdown. Infrastructure monitors only. + */ +export type VendorCounts = { + known?: number; + unknown?: number; +}; diff --git a/clients/admin-ui/src/types/api/models/WebResourceTypeCounts.ts b/clients/admin-ui/src/types/api/models/WebResourceTypeCounts.ts new file mode 100644 index 0000000000..68587de9ac --- /dev/null +++ b/clients/admin-ui/src/types/api/models/WebResourceTypeCounts.ts @@ -0,0 +1,16 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Resource type breakdown for website monitors. + * + * Keys match the existing ``WEB_MONITOR_RESOURCE_TYPE_API_MAPPING`` values. + */ +export type WebResourceTypeCounts = { + cookie?: number; + browser_request?: number; + image?: number; + iframe?: number; + javascript_tag?: number; +}; diff --git a/clients/fidesui/src/components/charts/DonutChart.tsx b/clients/fidesui/src/components/charts/DonutChart.tsx index 440548c1a0..3e6ff1346b 100644 --- a/clients/fidesui/src/components/charts/DonutChart.tsx +++ b/clients/fidesui/src/components/charts/DonutChart.tsx @@ -14,13 +14,14 @@ export interface DonutChartSegment { name?: string; } -export type DonutChartVariant = "default" | "thick"; +export type DonutChartVariant = "default" | "thick" | "thin"; export interface DonutChartProps { segments: DonutChartSegment[]; centerLabel?: ReactNode; variant?: DonutChartVariant; animationDuration?: number; + fit?: "contain" | "fill"; } export const DonutChart = ({ @@ -28,11 +29,13 @@ export const DonutChart = ({ centerLabel, variant = "default", animationDuration = CHART_ANIMATION.defaultDuration, + fit = "contain", }: DonutChartProps) => { const { token } = theme.useToken(); const containerRef = useRef(null); const { width, height } = useContainerSize(containerRef); - const size = Math.min(width, height); + const size = + fit === "contain" ? Math.min(width, height) : Math.max(width, height); const animationActive = useChartAnimation(animationDuration); const thickness = DONUT_THICKNESS[variant]; @@ -46,7 +49,10 @@ export const DonutChart = ({ })); return ( -
+
{size > 0 && ( , ): ContainerSize => { const [size, setSize] = useState({ width: 0, height: 0 }); + useEffect(() => { const el = ref.current; if (!el) {