From e5316331f0af1a2ad65a99e1880480ccdd8221db Mon Sep 17 00:00:00 2001 From: Sarah Gillespie <73313222+gillespi314@users.noreply.github.com> Date: Wed, 12 Jun 2024 13:49:18 -0500 Subject: [PATCH] Add exception to UI software name-to-icon mapping for ZoomInfo (#19509) --- changes/19000-zoominfo-icon | 1 + .../icons/SoftwareIcon/SoftwareIcon.tsx | 50 +++------- .../SoftwarePage/components/icons/index.ts | 99 ++++++++++++++++--- 3 files changed, 100 insertions(+), 50 deletions(-) create mode 100644 changes/19000-zoominfo-icon diff --git a/changes/19000-zoominfo-icon b/changes/19000-zoominfo-icon new file mode 100644 index 0000000000..08bd20c745 --- /dev/null +++ b/changes/19000-zoominfo-icon @@ -0,0 +1 @@ +- Fixed UI bug where Zoom icon was displayed for ZoomInfo. diff --git a/frontend/pages/SoftwarePage/components/icons/SoftwareIcon/SoftwareIcon.tsx b/frontend/pages/SoftwarePage/components/icons/SoftwareIcon/SoftwareIcon.tsx index e99b23e262..2e93fc9272 100644 --- a/frontend/pages/SoftwarePage/components/icons/SoftwareIcon/SoftwareIcon.tsx +++ b/frontend/pages/SoftwarePage/components/icons/SoftwareIcon/SoftwareIcon.tsx @@ -1,10 +1,5 @@ -import React, { ComponentType, SVGProps } from "react"; -import { - SOFTWARE_NAME_TO_ICON_MAP, - SOFTWARE_SOURCE_TO_ICON_MAP, - SOFTWARE_ICON_SIZES, - SoftwareIconSizes, -} from "../"; +import React from "react"; +import getMatchedSoftwareIcon from "../"; const baseClass = "software-icon"; @@ -14,43 +9,20 @@ interface ISoftwareIconProps { size?: SoftwareIconSizes; } -const matchInMap = ( - map: Record>>, - potentialKey?: string -) => { - if (!potentialKey) { - return null; - } +const SOFTWARE_ICON_SIZES: Record = { + medium: "24", + meduim_large: "64", // TODO: rename this to large and update large to xlarge + large: "96", +} as const; - const sanitizedKey = potentialKey.trim().toLowerCase(); - const match = Object.entries(map).find(([namePrefix, icon]) => { - if (sanitizedKey.startsWith(namePrefix)) { - return icon; - } - return null; - }); - - return match ? match[1] : null; -}; +type SoftwareIconSizes = keyof typeof SOFTWARE_ICON_SIZES; const SoftwareIcon = ({ - name, - source, + name = "", + source = "", size = "medium", }: ISoftwareIconProps) => { - // try to find a match for name - let MatchedIcon = matchInMap(SOFTWARE_NAME_TO_ICON_MAP, name); - - // otherwise, try to find a match for source - if (!MatchedIcon) { - MatchedIcon = matchInMap(SOFTWARE_SOURCE_TO_ICON_MAP, source); - } - - // default to 'package' - if (!MatchedIcon) { - MatchedIcon = SOFTWARE_SOURCE_TO_ICON_MAP.package; - } - + const MatchedIcon = getMatchedSoftwareIcon({ name, source }); return ( = { - medium: "24", - meduim_large: "64", // TODO: rename this to large and update large to xlarge - large: "96", -} as const; +/** + * This attempts to loosely match the provided string to a key in a provided dictionary, returning the key if the + * provided string starts with the key or undefined otherwise. + */ +const matchLoosePrefixToKey = >( + dict: T, + s: string +) => { + s = s.trim().toLowerCase(); + if (!s) { + return undefined; + } + const match = Object.keys(dict).find((k) => + s.startsWith(k.trim().toLowerCase()) + ); -export type SoftwareIconSizes = keyof typeof SOFTWARE_ICON_SIZES; + return match ? (match as keyof T) : undefined; +}; + +/** + * This strictly matches the provided name and source to a software icon, returning the icon if a match is found or + * null otherwise. It is intended to be used for special cases where a strict match is required + * (e.g. Zoom). The caller should handle null cases by falling back to loose matching on name prefixes. + */ +const matchStrictNameSourceToIcon = ({ + name = "", + source = "", +}: Pick) => { + name = name.trim().toLowerCase(); + source = source.trim().toLowerCase(); + switch (true) { + case name === "zoom.us.app" && source === "apps": + return Zoom; + case name === "zoom": + return Zoom; + case name === "google chrome": + return ChromeApp; + default: + return null; + } +}; + +/** + * This returns the icon component for a given software name and source. If a strict match is found, + * it will be returned, otherwise it will fall back to loose matching on name and source prefixes. + * If no match is found, the default package icon will be returned. + */ +const getMatchedSoftwareIcon = ({ + name = "", + source = "", +}: Pick) => { + // first, try strict matching on name and source + let Icon = matchStrictNameSourceToIcon({ + name, + source, + }); + + // if no match, try loose matching on name prefixes + if (!Icon) { + const matchedName = matchLoosePrefixToKey(SOFTWARE_NAME_TO_ICON_MAP, name); + if (matchedName) { + Icon = SOFTWARE_NAME_TO_ICON_MAP[matchedName]; + } + } + + // if still no match, try loose matching on source prefixes + if (!Icon) { + const matchedSource = matchLoosePrefixToKey( + SOFTWARE_SOURCE_TO_ICON_MAP, + source + ); + if (matchedSource) { + Icon = SOFTWARE_SOURCE_TO_ICON_MAP[matchedSource]; + } + } + + // if still no match, default to 'package' + if (!Icon) { + Icon = SOFTWARE_SOURCE_TO_ICON_MAP.package; + } + + return Icon; +}; + +export default getMatchedSoftwareIcon;