diff --git a/.eslintrc.js b/.eslintrc.js index 3c1a1e304b..a9018e47b4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,6 @@ module.exports = { env: { browser: true, es2015: true, node: true }, - settings: { "import/resolver": "webpack" }, + settings: { "import/resolver": "webpack", react: { version: "detect" } }, extends: ["eslint:recommended", "plugin:react/recommended", "prettier"], overrides: [ { diff --git a/declarations.d.ts b/declarations.d.ts index e2cd8e72a0..b8eab7bd34 100644 --- a/declarations.d.ts +++ b/declarations.d.ts @@ -5,7 +5,7 @@ declare module "*.module.scss" { export default classes; } -declare module '*.md' { +declare module "*.md" { const value: string; // markdown is just a string export default value; } diff --git a/docs/docs-content/tutorials/cluster-deployment/pcg/deploy-app-pcg.md b/docs/docs-content/tutorials/cluster-deployment/pcg/deploy-app-pcg.md index 6b5a3743f9..30809471eb 100644 --- a/docs/docs-content/tutorials/cluster-deployment/pcg/deploy-app-pcg.md +++ b/docs/docs-content/tutorials/cluster-deployment/pcg/deploy-app-pcg.md @@ -358,7 +358,8 @@ complete the core infrastructure stack. Next, click on the **Add New Pack** button to include add-on layers to your cluster profile. Add the **MetalLB (Helm)** pack to your profile. The - pack provides a load-balancer + + pack provides a load-balancer implementation for your Kubernetes cluster, as VMware does not offer a load balancer solution natively. The load balancer is required to help the _LoadBalancer_ service specified in the Hello Universe application manifest obtain an IP address, so that you can access the application from your browser. diff --git a/docs/docs-content/vm-management/vm-packs-profiles/create-vmo-profile.md b/docs/docs-content/vm-management/vm-packs-profiles/create-vmo-profile.md index 221582c1f5..6386453b10 100644 --- a/docs/docs-content/vm-management/vm-packs-profiles/create-vmo-profile.md +++ b/docs/docs-content/vm-management/vm-packs-profiles/create-vmo-profile.md @@ -109,7 +109,8 @@ components, refer to [Virtual Machine Orchestrator Pack](../vm-packs-profiles/vm 9. If your cluster profile does include a load balancer such as MetalLB, no changes are required and you can skip this step. For more information about MetalLB, refer to - . + + . If your cluster profile does not include a load balancer, update the services `charts.virtual-machine-orchestrator.kubevirt` and `charts.virtual-machine-orchestrator.cdi` to type ClusterIP in diff --git a/package.json b/package.json index 5848d997db..8f484db2c0 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "fuse.js": "^6.6.2", "markdown-to-jsx": "^7.0.0", "node-fetch": "^3.1.0", - "p-ratelimit":"^1.0.1", + "p-ratelimit": "^1.0.1", "prism-react-renderer": "^2.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/plugins/packs-integrations.js b/plugins/packs-integrations.js index da2e049af2..8e1847fa24 100644 --- a/plugins/packs-integrations.js +++ b/plugins/packs-integrations.js @@ -166,7 +166,7 @@ function sortVersions(tags) { return sortedVersions; } -function getAggregatedVersions(registries, repositories, packUidMap, packName) { +function getAggregatedVersions(registries, repositories, packUidMap) { const prefferedRegistryUid = repositories?.[0]?.uid; //if a pack has multiple registries, then the versions of the pack are aggregated based on the selected registries //if a same version in multiple registries, the preferred registry is the higher precendence. @@ -334,7 +334,7 @@ async function write(res, packName, logoUrlMap) { logoUrlMap[packName] = `${packName}.${mime.extension(type)}`; }); } else { - reject("Invalid Mime type for the logo"); + reject(`Invalid MIME type received for the logo ${packName}`); } }); } @@ -360,7 +360,11 @@ async function getLogoUrl(packsAllData, logoUrlMap) { await setTimeout(1000); } } - } catch (e) {} + } catch (e) { + // Intentionally ignoring errors here to continue processing other logos + // Enable the below line to log the error, if needed, for debugging. + // logger.error(e); + } } } } diff --git a/src/components/IconMapper/IconMapper.tsx b/src/components/IconMapper/IconMapper.tsx index 3f2f356c01..de9ac731f5 100644 --- a/src/components/IconMapper/IconMapper.tsx +++ b/src/components/IconMapper/IconMapper.tsx @@ -18,21 +18,21 @@ import RolesIcon from "@site/static/assets/icons/roles.svg"; import TeamsIcon from "@site/static/assets/icons/teams.svg"; import WorkspacesIcon from "@site/static/assets/icons/workspaces.svg"; import TerraformIcon from "@site/static/assets/icons/terraform.svg"; -import K8sIcon from '@site/static/assets/packs/k8s_layer.svg'; -import CniIcon from '@site/static/assets/packs/cni_layer.svg'; -import OsIcon from '@site/static/assets/packs/os_layer.svg'; -import ServiceMeshIcon from '@site/static/assets/packs/service_mesh_layer.svg'; -import MonitoringIcon from '@site/static/assets/packs/monitoring_layer.svg'; -import CsiIcon from '@site/static/assets/packs/csi_layer.svg'; -import LoggingIcon from '@site/static/assets/packs/logging_layer.svg'; -import LoadBalancerIcon from '@site/static/assets/packs/load_balancer_layer.svg'; -import IngressIcon from '@site/static/assets/packs/ingress_layer.svg'; -import AuthenticationIcon from '@site/static/assets/packs/authentication_layer.svg'; -import RegistryIcon from '@site/static/assets/packs/registry_layer.svg'; -import SystemAppIcon from '@site/static/assets/packs/system_app_layer.svg'; -import SecurityIcon from '@site/static/assets/packs/security_layer.svg'; -import AppServicesIcon from '@site/static/assets/packs/system_app_layer.svg'; -import MiscIcon from '@site/static/assets/packs/misc_layer.svg'; +import K8sIcon from "@site/static/assets/packs/k8s_layer.svg"; +import CniIcon from "@site/static/assets/packs/cni_layer.svg"; +import OsIcon from "@site/static/assets/packs/os_layer.svg"; +import ServiceMeshIcon from "@site/static/assets/packs/service_mesh_layer.svg"; +import MonitoringIcon from "@site/static/assets/packs/monitoring_layer.svg"; +import CsiIcon from "@site/static/assets/packs/csi_layer.svg"; +import LoggingIcon from "@site/static/assets/packs/logging_layer.svg"; +import LoadBalancerIcon from "@site/static/assets/packs/load_balancer_layer.svg"; +import IngressIcon from "@site/static/assets/packs/ingress_layer.svg"; +import AuthenticationIcon from "@site/static/assets/packs/authentication_layer.svg"; +import RegistryIcon from "@site/static/assets/packs/registry_layer.svg"; +import SystemAppIcon from "@site/static/assets/packs/system_app_layer.svg"; +import SecurityIcon from "@site/static/assets/packs/security_layer.svg"; +import AppServicesIcon from "@site/static/assets/packs/system_app_layer.svg"; +import MiscIcon from "@site/static/assets/packs/misc_layer.svg"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { fontAwesomeIcons } from "./dynamicFontAwesomeImports"; @@ -66,15 +66,15 @@ export const icons: IconsMap = { monitoring: , csi: , logging: , - 'load balancer': , + "load balancer": , ingress: , authentication: , registry: , - 'system app': , + "system app": , spectro: , security: , serverless: , - 'app services': , + "app services": , }; function IconMapper({ type }: { type: string }): React.ReactElement { diff --git a/src/components/Integrations/Packs/Packs.tsx b/src/components/Integrations/Packs/Packs.tsx index 2defc2e645..439b8c6ab2 100644 --- a/src/components/Integrations/Packs/Packs.tsx +++ b/src/components/Integrations/Packs/Packs.tsx @@ -5,5 +5,5 @@ import PacksIntegrationsPluginData from "../IntegrationTypes"; export default function Packs() { const { packs, repositories } = usePluginData("plugin-packs-integrations") as PacksIntegrationsPluginData; - return ; + return ; } diff --git a/src/components/PacksInformation/PacksInformation.tsx b/src/components/PacksInformation/PacksInformation.tsx index c2652e3f8c..80be2d66ab 100644 --- a/src/components/PacksInformation/PacksInformation.tsx +++ b/src/components/PacksInformation/PacksInformation.tsx @@ -1,17 +1,31 @@ import React from "react"; import PacksReadme from "@site/src/components/PacksReadme/PacksReadme"; -import { Switch, Redirect } from 'react-router-dom' +import { Switch, Redirect } from "react-router-dom"; -export default function Packs(props: any) { +interface Packs { + route: PacksData; +} + +interface PacksData { + data: { + name: string; + version: string; + parent: string; + }; +} + +export default function Packs(props: Packs) { return ( <> - {props?.route?.data ? - ( - - ) : ( - - ) - } + {props?.route?.data ? ( + + + + ) : ( + + )} ); } diff --git a/src/components/PacksReadme/PacksReadme.antd.css b/src/components/PacksReadme/PacksReadme.antd.css index 25feec7f3f..67b6d1299c 100644 --- a/src/components/PacksReadme/PacksReadme.antd.css +++ b/src/components/PacksReadme/PacksReadme.antd.css @@ -6,7 +6,7 @@ p { font-size: 16px; } -[data-theme = "dark"] .ant-select-tree-list-holder-inner { +[data-theme="dark"] .ant-select-tree-list-holder-inner { background-color: #1f1f1f; color: whitesmoke; } diff --git a/src/components/PacksReadme/PacksReadme.tsx b/src/components/PacksReadme/PacksReadme.tsx index a4c83561ef..c0a3622ad6 100644 --- a/src/components/PacksReadme/PacksReadme.tsx +++ b/src/components/PacksReadme/PacksReadme.tsx @@ -16,14 +16,39 @@ import Admonition from "@theme/Admonition"; interface PackReadmeProps { customDescription: string; - packUidMap: any; - versions: Array; + packUidMap: Record; + versions: Version[]; title: string; logoUrl: string; type: string; provider: Array; registries: Array; - selectedRepositories: Array; + selectedRepositories: Array<{ uid: string; name: string }>; + disabled: boolean; + latestVersion: string; +} +interface MarkdownFile { + default: React.FC; +} + +interface Version { + title: string; + children: Array<{ + title: string; + packUid: string; + }>; +} + +interface PackData { + customDescription: string; + packUidMap: Record; + versions: Version[]; + title: string; + logoUrl: string; + type: string; + provider: Array; + registries: Array; + selectedRepositories: Array<{ uid: string; name: string }>; disabled: boolean; latestVersion: string; } @@ -39,13 +64,14 @@ export default function PacksReadme() { const { defaultAlgorithm, darkAlgorithm } = theme; const [selectedVersion, setSelectedVersion] = useState(""); const history = useHistory(); + useEffect(() => { const searchParams = window ? new URLSearchParams(window.location.search) : null; const pckName = searchParams?.get("pack") || ""; setPackName(pckName); const importComponent = async () => { try { - const module = await import(`../../../docs/docs-content/integrations/${pckName}.md`); + const module: MarkdownFile = await import(`../../../docs/docs-content/integrations/${pckName}.md`); const PackReadMeComponent = module.default; setCustomReadme(
@@ -56,10 +82,12 @@ export default function PacksReadme() { setCustomReadme(null); } }; - importComponent(); + importComponent().catch((e) => { + console.error("Error importing custom readme component for pack. Additional information follows: \n", e); + }); }, []); - const packData = useMemo(() => { + const packData: PackData = useMemo(() => { const pack = packs.find((pack) => pack.name === packName); if (pack) { const packDataInfo: PackReadmeProps = { @@ -91,24 +119,20 @@ export default function PacksReadme() { latestVersion: "", }; }, [packName]); + useEffect(() => { const searchParams = window ? new URLSearchParams(window.location.search) : null; const urlParamVersion = searchParams?.get("version"); const version = urlParamVersion || packData?.latestVersion || packData?.versions[0]?.title || ""; - let parentVersionObj: any; if (version && !version.endsWith(".x")) { - parentVersionObj = getParentVersion(version); - const packDataObj = parentVersionObj?.children.find((child: any) => child.title === version); - setSelectedPackUid(packDataObj?.packUid || ""); - setSelectedVersion(version); + const parentVersionObj = getParentVersion(version); + const packDataObj = parentVersionObj?.children.find((child) => child.title === version); + if (packDataObj) { + setSelectedPackUid(packDataObj.packUid); + setSelectedVersion(version); + } } }, [packData]); - let warningContent; - if (packData.disabled) { - warningContent = "This pack is disabled. Upgrade to a newer version to take advantage of new features."; - } else if (selectedPackUid && packData.packUidMap[selectedPackUid]?.deprecated) { - warningContent = "This pack is deprecated. Upgrade to a newer version to take advantage of new features."; - } function versionChange(item: string) { const [version, packUid] = item.split("==="); @@ -119,32 +143,28 @@ export default function PacksReadme() { } function getParentVersion(version: string) { - return packData.versions.find((tagVersion) => tagVersion.children.find((child: any) => child.title === version)); + return packData.versions.find((tagVersion) => tagVersion.children.find((child) => child.title === version)); } function renderVersionOptions() { - return packData.versions.map((tagVersion) => { - return { - value: tagVersion.title, - title: tagVersion.title, - selectable: false, - children: tagVersion.children.map((child: any) => { - return { - value: `${child.title}===${child.packUid}`, - title: {child.title}, - }; - }), - }; - }); + return packData.versions.map((tagVersion) => ({ + value: tagVersion.title, + title: tagVersion.title, + selectable: false, + children: tagVersion.children.map((child) => ({ + value: `${child.title}===${child.packUid}`, + title: {child.title}, + })), + })); } function renderTabs() { - let readme = selectedPackUid ? packData.packUidMap[selectedPackUid]?.readme : ""; + const readme = selectedPackUid ? packData.packUidMap[selectedPackUid]?.readme : ""; const tabs = [ readme && { label: `README`, key: "1", - children: , + children: {readme}, }, customReadme && { label: `Additional Details`, @@ -156,13 +176,11 @@ export default function PacksReadme() { if (tabs.length > 1) { return ( - {tabs.map((item) => { - return ( - - {item.children} - - ); - })} + {tabs.map((item) => ( + + {item.children} + + ))} ); } @@ -195,12 +213,14 @@ export default function PacksReadme() { .map((provider) => cloudDisplayNames[provider as keyof typeof cloudDisplayNames] || provider) .join(", "); } + function getRegistries() { if (selectedVersion && !selectedVersion.endsWith(".x")) { - const registerUid = packData.packUidMap[selectedPackUid]?.registryUid || ""; - return packData.selectedRepositories.find((registry) => registry.uid === registerUid)?.name || ""; + const registryUid = packData.packUidMap[selectedPackUid]?.registryUid || ""; + const registry = packData.selectedRepositories.find((registry) => registry.uid === registryUid); + return registry ? registry.name : ""; } - const consolidatedRegistries = packData.registries.reduce((accumulator: string[], registry) => { + const consolidatedRegistries = packData.registries.reduce((accumulator, registry) => { const regObj = packData.selectedRepositories.find((repo) => repo.uid === registry); if (regObj) { accumulator.push(regObj.name); @@ -210,6 +230,8 @@ export default function PacksReadme() { return consolidatedRegistries.join(", "); } + console.log("packData", packData); + return (
@@ -230,23 +252,27 @@ export default function PacksReadme() { dropdownStyle={{ maxHeight: 400, overflow: "auto" }} placeholder="Search" treeDefaultExpandAll - onChange={(item) => versionChange(item as string)} + onChange={(item) => versionChange(item)} treeData={renderVersionOptions()} />
-
- {`Type: ${packTypeNames[packData.type as keyof typeof packTypeNames]}`} -
+
{`Type: ${packTypeNames[packData.type]}`}
{`Cloud Providers: ${getProviders()}`}
{`Registry: ${getRegistries()}`}
- {warningContent ? ( + {packData.disabled ? ( + + Pack version v{selectedVersion} is disabled. Upgrade to a newer version to take advantage + of new features. + + ) : selectedPackUid && packData.packUidMap[selectedPackUid]?.deprecated ? ( - {warningContent} + Pack version v{selectedVersion} is deprecated. Upgrade to a newer version to take advantage + of new features. ) : null}
diff --git a/src/components/PacksTable/PacksTable.tsx b/src/components/PacksTable/PacksTable.tsx index f96534c7ab..685daaa994 100644 --- a/src/components/PacksTable/PacksTable.tsx +++ b/src/components/PacksTable/PacksTable.tsx @@ -137,6 +137,7 @@ const FilteredTable: React.FC = () => { const [deprecatedPacks, setDeprecatedPacks] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); + const [timer, setTimer] = useState(null); useEffect(() => { fetch("/packs-data/packs_report.json") @@ -163,15 +164,20 @@ const FilteredTable: React.FC = () => { }); }, []); - const handleSearch = useCallback((e: React.ChangeEvent) => { - const value = e.target.value; - // Delay the update to the search value by 300ms to debounce the search - const timer = setTimeout(() => { - setSearchValue(value); - }, 300); + const handleSearch = useCallback( + (searchString: string) => { + if (timer) { + clearTimeout(timer); + } - return () => clearTimeout(timer); - }, []); + const newTimer = setTimeout(() => { + setSearchValue(searchString); + }, 300); + + setTimer(newTimer); + }, + [timer] + ); const filteredPacks = searchValue ? deprecatedPacks.filter((pack) => pack.displayName.toLowerCase().includes(searchValue.toLowerCase())) diff --git a/src/components/Technologies/AppPackList.tsx b/src/components/Technologies/AppPackList.tsx index 304cb72912..ada0f10d58 100644 --- a/src/components/Technologies/AppPackList.tsx +++ b/src/components/Technologies/AppPackList.tsx @@ -70,9 +70,9 @@ export default function Technologies({ data }: TechnologiesProps) {
- {technologies.map(( field ) => { + {technologies.map((field) => { const { title, slug, logoUrl } = field; - return ; + return ; })}
diff --git a/src/components/Technologies/CategorySelector/CustomLabel.tsx b/src/components/Technologies/CategorySelector/CustomLabel.tsx index 28b6dd60b3..162fda9468 100644 --- a/src/components/Technologies/CategorySelector/CustomLabel.tsx +++ b/src/components/Technologies/CategorySelector/CustomLabel.tsx @@ -9,9 +9,7 @@ interface CustomLabelProps { export default function CustomLabel({ label, className = "" }: CustomLabelProps) { return ( <> -
- {label} -
+
{label}
); } diff --git a/src/components/Technologies/CategorySelector/FilterSelect.tsx b/src/components/Technologies/CategorySelector/FilterSelect.tsx index e2000e0982..58d091f465 100644 --- a/src/components/Technologies/CategorySelector/FilterSelect.tsx +++ b/src/components/Technologies/CategorySelector/FilterSelect.tsx @@ -5,17 +5,17 @@ import "./filterSelect.antd.css"; interface FilterSelectProps { selectMode?: SelectProps["mode"]; - options: { value: string, label: string }[]; + options: { value: string; label: string }[]; onChange: (...args: any) => void; value: SelectProps["value"]; -}; +} export default function FilterSelect({ selectMode, options, onChange, value }: FilterSelectProps) { return (
diff --git a/src/components/Technologies/CategorySelector/filterSelect.antd.css b/src/components/Technologies/CategorySelector/filterSelect.antd.css index f56381ffa3..28bc723909 100644 --- a/src/components/Technologies/CategorySelector/filterSelect.antd.css +++ b/src/components/Technologies/CategorySelector/filterSelect.antd.css @@ -1,33 +1,36 @@ -[data-theme = "dark"] .ant-select-outlined:not(.ant-select-customize-input) .ant-select-selector { +[data-theme="dark"] .ant-select-outlined:not(.ant-select-customize-input) .ant-select-selector { border: 1px solid #424242; background: #141414; } -[data-theme = "dark"] .ant-select-selection-placeholder { +[data-theme="dark"] .ant-select-selection-placeholder { color: #6a6d76; } -[data-theme = "dark"] .ant-select-arrow { +[data-theme="dark"] .ant-select-arrow { color: #6a6d76; } -[data-theme = "dark"] .ant-select-item-option { +[data-theme="dark"] .ant-select-item-option { color: whitesmoke; } -[data-theme = "dark"] .rc-virtual-list { +[data-theme="dark"] .rc-virtual-list { background-color: #1f1f1f; } -[data-theme = "dark"] .ant-select-dropdown { +[data-theme="dark"] .ant-select-dropdown { background-color: #1f1f1f; } -[data-theme = "dark"] .ant-select-dropdown .ant-select-item-option-selected:not(.ant-select-item-option-disabled) { +[data-theme="dark"] .ant-select-dropdown .ant-select-item-option-selected:not(.ant-select-item-option-disabled) { background-color: #111a2c; color: whitesmoke; } -[data-theme = "dark"] .ant-select-dropdown .ant-select-item-option-selected:not(.ant-select-item-option-disabled) .ant-select-item-option-state { +[data-theme="dark"] + .ant-select-dropdown + .ant-select-item-option-selected:not(.ant-select-item-option-disabled) + .ant-select-item-option-state { color: #1668dc; } -[data-theme = "dark"] .ant-select:not(.ant-select-customize-input) .ant-select-selector { +[data-theme="dark"] .ant-select:not(.ant-select-customize-input) .ant-select-selector { background-color: #1f1f1f; } -[data-theme = "dark"] .ant-select-multiple { +[data-theme="dark"] .ant-select-multiple { .ant-select-selection-item { color: #d6d0d0; background-color: #313131; @@ -36,14 +39,14 @@ color: #d6d0d0; } } -[dark-theme = "dark"] .ant-select-multiple .ant-select-selection-overflow .ant-select-selection-item-remove { +[dark-theme="dark"] .ant-select-multiple .ant-select-selection-overflow .ant-select-selection-item-remove { color: #d6d0d0; } -[data-theme = "dark"] .ant-select-clear { +[data-theme="dark"] .ant-select-clear { color: #b1adad; background-color: #1f1f1f; } -[data-theme = "dark"] .ant-select-single .ant-select-selector { +[data-theme="dark"] .ant-select-single .ant-select-selector { color: #d6d0d0; background-color: #1f1f1f; } diff --git a/src/components/Technologies/PackCardIcon.tsx b/src/components/Technologies/PackCardIcon.tsx index d17279142d..e6ae29aa82 100644 --- a/src/components/Technologies/PackCardIcon.tsx +++ b/src/components/Technologies/PackCardIcon.tsx @@ -14,22 +14,18 @@ export default function PackCardIcon({ appType, logoUrl, type, className }: Pack useEffect(() => { if (logoUrl) { try { - if(appType === "app") { + if (appType === "app") { setIcon(); } else { setIcon(); } } catch (e) { - type ? setIcon() : setIcon(null) + type ? setIcon() : setIcon(null); } } else { - type ? setIcon() : setIcon(null) + type ? setIcon() : setIcon(null); } }, [logoUrl]); - return ( -
- {icon} -
- ); + return
{icon}
; } diff --git a/src/components/Technologies/PacksFilters.tsx b/src/components/Technologies/PacksFilters.tsx index d428c886b6..28ea4b8256 100644 --- a/src/components/Technologies/PacksFilters.tsx +++ b/src/components/Technologies/PacksFilters.tsx @@ -4,21 +4,39 @@ import CustomLabel from "./CategorySelector/CustomLabel"; import FilterSelect from "./CategorySelector/FilterSelect"; import "./packsFilters.antd.css"; import { packTypeNames, cloudProviderTypes } from "../../constants/packs"; + interface PackFiltersProps { categories: string[]; - registries: any[]; - setSelectedSearchFilters: (...args: any[]) => void; - selectedFilters: any; + registries: registry[]; + setSelectedSearchFilters: (filters: SelectedFilters) => void; + selectedFilters: SelectedFilters; +} + +interface registry { + uid: string; + name: string; +} + +interface sources { + label: string; + value: string; } -const sourceList: any[] = [ +interface SelectedFilters { + category: string[]; + registries: string[]; + cloudTypes: string[]; + source: string[]; +} + +const sourceList: sources[] = [ { label: "All", - value: "all" + value: "all", }, { label: "Verified", - value: "verified" + value: "verified", }, { label: "Community", @@ -26,19 +44,22 @@ const sourceList: any[] = [ }, ]; -export default function PacksFilters({ categories, registries, setSelectedSearchFilters, selectedFilters }: PackFiltersProps) { - - +export default function PacksFilters({ + categories, + registries, + setSelectedSearchFilters, + selectedFilters, +}: PackFiltersProps) { return (
{ - return { value: category, label: packTypeNames[category as keyof typeof packTypeNames] }; + options={categories.map((category: string) => { + return { value: category, label: packTypeNames[category] }; })} - onChange={(items) => setSelectedSearchFilters({ category: items })} + onChange={(items) => setSelectedSearchFilters({ ...selectedFilters, category: items as string[] })} value={selectedFilters.category} />
@@ -46,10 +67,10 @@ export default function PacksFilters({ categories, registries, setSelectedSearch { + options={registries.map((registry) => { return { value: registry.uid, label: registry.name }; })} - onChange={(items) => setSelectedSearchFilters({ registries: items })} + onChange={(items) => setSelectedSearchFilters({ ...selectedFilters, registries: items as string[] })} value={selectedFilters.registries} />
@@ -60,11 +81,10 @@ export default function PacksFilters({ categories, registries, setSelectedSearch return { value: provider.name, label: provider.displayName }; })} onChange={(item) => { - if (item) { - setSelectedSearchFilters({ cloudTypes: [item] }) - } else { - setSelectedSearchFilters({ cloudTypes: [] }) - } + setSelectedSearchFilters({ + ...selectedFilters, + cloudTypes: item ? [item as string] : [], + }); }} value={selectedFilters.cloudTypes.length ? selectedFilters.cloudTypes[0] : undefined} /> @@ -74,15 +94,14 @@ export default function PacksFilters({ categories, registries, setSelectedSearch { - if (item) { - setSelectedSearchFilters({ source: [item] }) - } else { - setSelectedSearchFilters({ source: [] }) - } + setSelectedSearchFilters({ + ...selectedFilters, + source: item ? [item as string] : [], + }); }} value={selectedFilters.source.length ? selectedFilters.source[0] : undefined} /> - + ); } diff --git a/src/components/Technologies/Technologies.test.tsx b/src/components/Technologies/Technologies.test.tsx index c7ffc9be58..b40922afb0 100644 --- a/src/components/Technologies/Technologies.test.tsx +++ b/src/components/Technologies/Technologies.test.tsx @@ -1,6 +1,7 @@ import React from "react"; import { render, screen, fireEvent, waitFor } from "@testing-library/react"; import Technologies from "./Technologies"; // Replace with your actual import +import { FrontMatterData, RepositoryData } from "../Integrations/IntegrationTypes"; /* * TODO: Docusaurus Link import not resolving in Jest, @@ -20,38 +21,61 @@ jest.mock("./TechnologyCard", () => { }); }); -const mockData = [ +const mockData: FrontMatterData[] = [ { - fields: { - title: "React", - description: "A JavaScript library for building user interfaces", - hide_table_of_contents: false, - type: "library", - category: ["frontend"], - logoUrl: "react-logo.png", - slug: "/react", - id: "1", - sidebar_label: "React.js", - }, + name: "React", + title: "React", + description: "A JavaScript library for building user interfaces", + type: "library", + category: ["frontend"], + logoUrl: "react-logo.png", + slug: "/react", + id: "1", + packType: "library", // Example value, adjust as needed + verified: true, // Example value, adjust as needed + cloudTypes: ["cloudA", "cloudB"], // Example values, adjust as needed + packUidMap: new Map(), // Initialize as empty map + versions: [], // Initialize as empty array + community: true, // Example value, adjust as needed + registries: ["npm"], // Example values, adjust as needed + disabled: false, // Example value, adjust as needed + latestVersion: "17.0.2", // Example value, adjust as needed }, { - fields: { - title: "Node.js", - description: "JavaScript runtime built on Chrome's V8 JavaScript engine", - hide_table_of_contents: false, - type: "runtime", - category: ["backend"], - logoUrl: "nodejs-logo.png", - slug: "/nodejs", - id: "2", - sidebar_label: "Node", - }, + name: "Node.js", + title: "Node.js", + description: "JavaScript runtime built on Chrome's V8 JavaScript engine", + type: "runtime", + category: ["backend"], + logoUrl: "nodejs-logo.png", + slug: "/nodejs", + id: "2", + packType: "runtime", // Example value, adjust as needed + verified: true, // Example value, adjust as needed + cloudTypes: ["cloudA", "cloudB"], // Example values, adjust as needed + packUidMap: new Map(), // Initialize as empty map + versions: [], // Initialize as empty array + community: true, // Example value, adjust as needed + registries: ["64eaff453040297344bcad5d", "5eecc89d0b150045ae661cef"], // Example values, adjust as needed + disabled: false, // Example value, adjust as needed + latestVersion: "14.17.0", // Example value, adjust as needed + }, +]; + +const mockRepositories: RepositoryData[] = [ + { + name: "Palette Registry", + uid: "64eaff453040297344bcad5d", + }, + { + name: "Community Registry", + uid: "5eecc89d0b150045ae661cef", }, ]; describe("", () => { it("should filter technologies based on search", async () => { - render(); + render(); fireEvent.change(screen.getByRole("textbox"), { target: { value: "React" } }); @@ -61,39 +85,4 @@ describe("", () => { expect(screen.getByText("React")).toBeInTheDocument(); expect(screen.queryByText("Node.js")).not.toBeInTheDocument(); }); - - it("searches the technologies and checks if React is rendered", async () => { - render(); - - // Simulate a user clicking the "frontend" category - fireEvent.click(screen.getByText("frontend")); - - // Wait for the DOM to update - await waitFor(() => {}, { timeout: 300 }); - - // Check if React is displayed after selecting the "frontend" category - expect(screen.getByText("React")).toBeInTheDocument(); - - // Optionally, check if other technologies are not displayed - expect(screen.queryByText("Node.js")).not.toBeInTheDocument(); - }); - - it("searches the technologies using the input field and checks if React.js is rendered with Fuse search", async () => { - render(); - - // Get the input field by its role - const searchInput = screen.getByRole("textbox"); - - // Simulate a user typing "React.js" into the search input - fireEvent.change(searchInput, { target: { value: "React.js" } }); - - // Wait for the DOM to update - await waitFor(() => {}, { timeout: 300 }); - - // Check if React.js is displayed after searching for it - expect(screen.getByText("React")).toBeInTheDocument(); - - // Optionally, check if other technologies are not displayed - expect(screen.queryByText("Node.js")).not.toBeInTheDocument(); - }); }); diff --git a/src/components/Technologies/Technologies.tsx b/src/components/Technologies/Technologies.tsx index 2312ce0278..04c72a1bd2 100644 --- a/src/components/Technologies/Technologies.tsx +++ b/src/components/Technologies/Technologies.tsx @@ -6,11 +6,11 @@ import { FrontMatterData } from "../Integrations/IntegrationTypes"; import TechnologyCard from "./TechnologyCard"; import PacksFilters from "./PacksFilters"; import { packTypeNames, packTypes } from "../../constants/packs"; -import { Collapse} from "antd"; +import { Collapse } from "antd"; import "./technologies.antd.css"; import IconMapper from "../IconMapper/IconMapper"; -const searchOptions = { +const searchOptions: Fuse.IFuseOptions = { threshold: 0.5, keys: ["title"], }; @@ -20,86 +20,105 @@ interface TechnologiesProps { repositories: any[]; } +interface SelectedFilters { + category: string[]; + registries: string[]; + cloudTypes: string[]; + source: string[]; +} + const PACKLISTFILTERS = "packListFilters"; export default function Technologies({ data, repositories }: TechnologiesProps) { - const [selectedFilters, setSelectedFilters] = useState<{ category: any[], registries: any[], cloudTypes: any[], source: any[] }>({ category: [], registries: [], cloudTypes: [], source: ["all"] }) + const [selectedFilters, setSelectedFilters] = useState({ + category: [], + registries: [], + cloudTypes: [], + source: ["all"], + }); + const [searchValue, setSearchValue] = useState(""); + const filteredTechCards = useMemo(() => { - const selectedFiltersKeys = Object.keys(selectedFilters) - let filteredCards: any[] = []; - - const conditions = selectedFiltersKeys.reduce((acc, key) => { - const selectedFiltersValue = selectedFilters[key as keyof typeof selectedFilters]; - if (selectedFiltersValue.length) { - let condition; - if (selectedFiltersValue && selectedFiltersValue.length) { - switch (key) { - case "category": - condition = (techCard: FrontMatterData) => { - return selectedFiltersValue.includes(techCard.packType); - } - break; - case "registries": - condition = (techCard: FrontMatterData) => { - return selectedFiltersValue.some((value) => techCard.registries.includes(value)); - } - break; - case "cloudTypes": - condition = (techCard: FrontMatterData) => { - return selectedFiltersValue.some((value) => techCard.cloudTypes.includes("all") || techCard.cloudTypes.includes(value)); - } - break; - case "source": - if (!selectedFiltersValue.includes("all")) { + const selectedFiltersKeys = Object.keys(selectedFilters) as (keyof SelectedFilters)[]; + let filteredCards: FrontMatterData[] = []; + const conditions = selectedFiltersKeys.reduce( + (acc: ((techCard: FrontMatterData) => boolean)[], key) => { + const selectedFiltersValue = selectedFilters[key]; + if (selectedFiltersValue.length) { + let condition: (techCard: FrontMatterData) => boolean = () => false; + if (selectedFiltersValue && selectedFiltersValue.length) { + switch (key) { + case "category": condition = (techCard: FrontMatterData) => { - return techCard[selectedFiltersValue[0] as keyof FrontMatterData]; + return selectedFiltersValue.includes(techCard.packType); + }; + break; + case "registries": + condition = (techCard: FrontMatterData) => { + return selectedFiltersValue.some((value) => techCard.registries.includes(value)); + }; + break; + case "cloudTypes": + condition = (techCard: FrontMatterData) => { + return selectedFiltersValue.some( + (value) => techCard.cloudTypes.includes("all") || techCard.cloudTypes.includes(value) + ); + }; + break; + case "source": + if (!selectedFiltersValue.includes("all")) { + condition = (techCard: FrontMatterData) => { + return !!techCard[selectedFiltersValue[0] as keyof FrontMatterData]; + }; + } else { + condition = () => true; } - } - break; - } - if (condition) { - acc.push(condition) + break; + } + if (condition) { + acc.push(condition); + } } } - } - return acc; - }, new Array()); + return acc; + }, + [] as ((techCard: FrontMatterData) => boolean)[] + ); + filteredCards = data.filter((card) => { if (conditions.length) { - return conditions.every((condition) => { - return condition(card); - }) + return conditions.every((condition) => condition(card)); } else { return true; } - }) + }); + if (searchValue) { const fuse = new Fuse(filteredCards, searchOptions); filteredCards = fuse.search(searchValue).map(({ item }) => item); } - const categoriesMap = filteredCards.reduce((acc: Map, technology: FrontMatterData) => { - let packType = technology.packType; + + const categoriesMap = filteredCards.reduce((acc: Map, technology: FrontMatterData) => { + const packType = technology.packType; if (acc.has(packType)) { - acc.get(packType).push(technology); + acc.get(packType)!.push(technology); } else { acc.set(packType, [technology]); } return acc; - }, new Map()); - - const sortedCategoriesMap = new Map([...categoriesMap.entries()].sort((field1: string, field2: string) => { - const packType1: keyof typeof packTypeNames = field1; - const packType2: keyof typeof packTypeNames = field2; - return packTypeNames[field1[0]].localeCompare(packTypeNames[field2[0]]); - })); - const categoryKeys = Array.from(sortedCategoriesMap.keys()) as string[]; - categoryKeys.forEach((category) => { - let techCards: any = sortedCategoriesMap.get(category); - techCards.sort((a: FrontMatterData, b: FrontMatterData) => { - return (a.title.localeCompare(b.title)) - }); + }, new Map()); + + const sortedCategoriesMap = new Map( + [...categoriesMap.entries()].sort(([key1], [key2]) => { + return packTypeNames[key1].localeCompare(packTypeNames[key2]); + }) + ); + + sortedCategoriesMap.forEach((techCards) => { + techCards.sort((a, b) => a.title.localeCompare(b.title)); }); + return sortedCategoriesMap; }, [data, selectedFilters, searchValue]); @@ -107,75 +126,92 @@ export default function Technologies({ data, repositories }: TechnologiesProps) const filters = localStorage.getItem(PACKLISTFILTERS); if (filters) { try { - const { selectedFilters, searchValue } = JSON.parse(filters); - setSelectedFilters(selectedFilters); - setSearchValue(searchValue || ""); + const parsedFilters = JSON.parse(filters) as { selectedFilters: SelectedFilters; searchValue: string }; + setSelectedFilters(parsedFilters.selectedFilters); + setSearchValue(parsedFilters.searchValue || ""); } catch (e) { console.error("Error in parsing filters from local storage", e); } } else { setFiltersInLocalStorage({ selectedFilters: selectedFilters, - searchValue: "" - }) + searchValue: "", + }); } }, []); const renderPacksCategories = () => { - let categoryKeys: string[] = Array.from(filteredTechCards.keys()) as string[]; - const renderedCategoryItems = categoryKeys.map((category) => { - const categoryItems = filteredTechCards.get(category) as FrontMatterData[]; + const categoryKeys = Array.from(filteredTechCards.keys()); + return categoryKeys.map((category) => { + const categoryItems = filteredTechCards.get(category)!; if (categoryItems.length) { - const obj = ({ - categoryItems.map((field) => { - const { title, logoUrl, packType, name, latestVersion, versions } = field; - return - }) - }) - return obj; + return ( + + {categoryItems.map((field) => { + const { title, logoUrl, packType, name, latestVersion, versions } = field; + return ( + + ); + })} + + ); } + return null; }); - return renderedCategoryItems; }; + function addPanelHeader(category: string) { return ( <> - {packTypeNames[category as keyof typeof packTypeNames]} + {packTypeNames[category]} ); } - const setSelectedSearchFilters = (selectedSearchFilters: Record) => { + const setSelectedSearchFilters = (selectedSearchFilters: Partial) => { const updatedFilters = { ...selectedFilters, - ...selectedSearchFilters + ...selectedSearchFilters, }; setFiltersInLocalStorage({ selectedFilters: updatedFilters, - searchValue: searchValue - }) + searchValue: searchValue, + }); setSelectedFilters(updatedFilters); - } + }; const onSearch = (value: string) => { setFiltersInLocalStorage({ selectedFilters: selectedFilters, - searchValue: value - }) + searchValue: value, + }); setSearchValue(value); - } + }; - const setFiltersInLocalStorage = (filters: any) => { + const setFiltersInLocalStorage = (filters: { selectedFilters: SelectedFilters; searchValue: string }) => { localStorage.setItem(PACKLISTFILTERS, JSON.stringify(filters)); - } + }; return (
- +
- + {renderPacksCategories()}
diff --git a/src/components/Technologies/TechnologyCard.tsx b/src/components/Technologies/TechnologyCard.tsx index 345e9fc224..173a2ba90e 100644 --- a/src/components/Technologies/TechnologyCard.tsx +++ b/src/components/Technologies/TechnologyCard.tsx @@ -3,6 +3,12 @@ import styles from "./Technologies.module.scss"; import PackCardIcon from "./PackCardIcon"; import Link from "@docusaurus/Link"; +interface Version { + title: string; + children: { + title: string; + }[]; +} interface TechnologyCardProps { name?: string; title: string; @@ -10,11 +16,12 @@ interface TechnologyCardProps { type?: string; slug?: string; version?: string; - versions?: any; + versions?: Version[]; } export default function TechnologyCard({ name, title, logoUrl, type, slug, version, versions }: TechnologyCardProps) { - const parentVersion = versions?.find((tagVersion: any) => tagVersion.children.find((child: any) => child.title === version))?.title || ""; + const parentVersion = + versions?.find((tagVersion) => tagVersion.children.find((child) => child.title === version))?.title || ""; return (
diff --git a/src/components/Technologies/packsFilters.antd.css b/src/components/Technologies/packsFilters.antd.css index 2de6e810a7..c3320792be 100644 --- a/src/components/Technologies/packsFilters.antd.css +++ b/src/components/Technologies/packsFilters.antd.css @@ -1,3 +1,3 @@ .ant-select .ant-select-selector { - border-radius:0px; + border-radius: 0px; } diff --git a/src/components/Technologies/technologies.antd.css b/src/components/Technologies/technologies.antd.css index 3ee3260b09..3bf9b3843a 100644 --- a/src/components/Technologies/technologies.antd.css +++ b/src/components/Technologies/technologies.antd.css @@ -6,8 +6,8 @@ display: flex; flex-wrap: wrap; } -.ant-menu-horizontal >.ant-menu-submenu:hover::after { - border-bottom-color: transparent !important +.ant-menu-horizontal > .ant-menu-submenu:hover::after { + border-bottom-color: transparent !important; } [data-theme="dark"] .ant-collapse-header-text { color: whitesmoke; diff --git a/src/constants/packs.ts b/src/constants/packs.ts index 2db7ded672..57884d639c 100644 --- a/src/constants/packs.ts +++ b/src/constants/packs.ts @@ -15,7 +15,7 @@ export const packTypes = [ "system app", ] as const; -type packType = (typeof packTypes)[number]; +// type packType = (typeof packTypes)[number]; export const packTypeNames: Record = { "app services": "App Services", diff --git a/src/declarations.d.ts b/src/declarations.d.ts index 2aeaba6224..a9da8662dd 100644 --- a/src/declarations.d.ts +++ b/src/declarations.d.ts @@ -4,7 +4,7 @@ declare module "*.module.scss"; declare module "*.png"; -declare module '*.md' { +declare module "*.md" { const value: string; // markdown is just a string export default value; } diff --git a/src/types/index.d.ts b/src/types/index.d.ts index ef7f29bd74..b22afd025e 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -4,7 +4,7 @@ declare module "*.module.scss"; declare module "*.png"; -declare module '*.md' { +declare module "*.md" { const value: string; // markdown is just a string export default value; } diff --git a/static/scripts/constants.js b/static/scripts/constants.js index f4424021a8..47e12617c1 100644 --- a/static/scripts/constants.js +++ b/static/scripts/constants.js @@ -1 +1 @@ -export const BASE_URL = 'http://api.spectrocloud.com'; +export const BASE_URL = "http://api.spectrocloud.com"; diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 80de3b1401..9de14de20c 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -3,7 +3,15 @@ "noEmit": true, "allowJs": true }, - "extends": "./tsconfig.json", - "include": ["src/**/*.ts", "src/**/*.tsx", "./.eslintrc.js", "*.d.ts", "visuals/**/*.ts", "playwright.config.ts"], + "extends": ["./tsconfig.json"], + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "./.eslintrc.js", + "*.d.ts", + "visuals/**/*.ts", + "playwright.config.ts", + "_partials/*.ts" + ], "exclude": ["**/node_modules", "deprecated"] }