diff --git a/frontend/package.json b/frontend/package.json index 74b378ecd3b..4a526412dd4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,7 +20,7 @@ "check-cycles": "CHECK_CYCLES=true yarn dev-once", "coverage": "jest --coverage .", "eslint": "node ./node_modules/.bin/eslint --max-warnings ${MAX_WARNINGS:-0} --color", - "lint": "NODE_OPTIONS=--max-old-space-size=4096 MAX_WARNINGS=341 yarn eslint --format ./scripts/eslint-exact-warnings.js .", + "lint": "NODE_OPTIONS=--max-old-space-size=4096 MAX_WARNINGS=312 yarn eslint --format ./scripts/eslint-exact-warnings.js .", "gherkin-lint": "./node_modules/.bin/gherkin-lint -c ./packages/dev-console/integration-tests/.gherkin-lintrc ./packages/*/integration-tests/features", "test": "LANG=en_US.UTF-8 jest", "debug-test": "node --inspect-brk node_modules/.bin/jest --runInBand", diff --git a/frontend/packages/console-app/src/components/console-operator/ConsolePluginsTable.tsx b/frontend/packages/console-app/src/components/console-operator/ConsolePluginsTable.tsx index add8deeaa69..62396b9b4bb 100644 --- a/frontend/packages/console-app/src/components/console-operator/ConsolePluginsTable.tsx +++ b/frontend/packages/console-app/src/components/console-operator/ConsolePluginsTable.tsx @@ -474,10 +474,7 @@ const useConsolePluginRows = (enabledPlugins: string[]) => { }; const PluginsPage: FC = (props) => { - const enabledPlugins = useMemo( - () => props?.obj?.spec?.plugins ?? [], - [props?.obj?.spec?.plugins], - ); + const enabledPlugins = props?.obj?.spec?.plugins ?? []; const { rows, loaded } = useConsolePluginRows(enabledPlugins); return ( diff --git a/frontend/packages/console-app/src/components/nodes/configuration/node-storage/PersistentVolumes.tsx b/frontend/packages/console-app/src/components/nodes/configuration/node-storage/PersistentVolumes.tsx index 88532f3ddcc..85fc8ca4e03 100644 --- a/frontend/packages/console-app/src/components/nodes/configuration/node-storage/PersistentVolumes.tsx +++ b/frontend/packages/console-app/src/components/nodes/configuration/node-storage/PersistentVolumes.tsx @@ -55,7 +55,7 @@ const PersistentVolumeRow: FC = ({ persistentVolumeDat ), ); return podsForPVC ? getCurrentPod(podsForPVC) : undefined; - }, [persistentVolumeData.vmi, persistentVolumeData.persistentVolumeClaim?.metadata.name, pods]); + }, [persistentVolumeData, pods]); return ( diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/app/components/utils/camel-case-wrap.tsx b/frontend/packages/console-dynamic-plugin-sdk/src/app/components/utils/camel-case-wrap.tsx index c445ae9a53d..2bcd474e4e2 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/app/components/utils/camel-case-wrap.tsx +++ b/frontend/packages/console-dynamic-plugin-sdk/src/app/components/utils/camel-case-wrap.tsx @@ -1,20 +1,14 @@ import type { FC } from 'react'; import { Fragment } from 'react'; -const MEMO = {}; - const CamelCaseWrap: FC = ({ value, dataTest }) => { if (!value) { return '-'; } - if (MEMO[value]) { - return MEMO[value]; - } - // Add word break points before capital letters (but keep consecutive capital letters together). const words = value.match(/[A-Z]+[^A-Z]*|[^A-Z]+/g); - const rendered = ( + return ( {words.map((word, i) => ( // eslint-disable-next-line react/no-array-index-key @@ -25,8 +19,6 @@ const CamelCaseWrap: FC = ({ value, dataTest }) => { ))} ); - MEMO[value] = rendered; - return rendered; }; type CamelCaseWrapProps = { diff --git a/frontend/packages/console-shared/src/components/formik-fields/NumberSpinnerField.tsx b/frontend/packages/console-shared/src/components/formik-fields/NumberSpinnerField.tsx index 512f9d97741..96d7b2bd0da 100644 --- a/frontend/packages/console-shared/src/components/formik-fields/NumberSpinnerField.tsx +++ b/frontend/packages/console-shared/src/components/formik-fields/NumberSpinnerField.tsx @@ -1,5 +1,4 @@ import type { FC, ReactEventHandler } from 'react'; -import { useCallback } from 'react'; import { FormGroup, FormHelperText, HelperText, HelperTextItem } from '@patternfly/react-core'; import type { FormikValues } from 'formik'; import { useField, useFormikContext } from 'formik'; @@ -31,18 +30,15 @@ export const NumberSpinnerField: FC = ({ useFormikValidationFix(field.value); - const handleChange: ReactEventHandler = useCallback( - (event) => { - field.onChange(event); - setFieldValue( - props.name, - props?.setOutputAsIntegerFlag - ? _.toInteger(event.currentTarget.value) - : event.currentTarget.value, - ); - }, - [field, props.name, setFieldValue, props?.setOutputAsIntegerFlag], - ); + const handleChange: ReactEventHandler = (event) => { + field.onChange(event); + setFieldValue( + props.name, + props?.setOutputAsIntegerFlag + ? _.toInteger(event.currentTarget.value) + : event.currentTarget.value, + ); + }; return ( diff --git a/frontend/packages/console-shared/src/components/progressive-list/ProgressiveListFooter.tsx b/frontend/packages/console-shared/src/components/progressive-list/ProgressiveListFooter.tsx index 51aa71a841c..7c7f11bfa22 100644 --- a/frontend/packages/console-shared/src/components/progressive-list/ProgressiveListFooter.tsx +++ b/frontend/packages/console-shared/src/components/progressive-list/ProgressiveListFooter.tsx @@ -18,32 +18,27 @@ export const ProgressiveListFooter: FC = ({ return null; } - const formattedString = new Intl.ListFormat(getLastLanguage() || 'en', { + const parts = new Intl.ListFormat(getLastLanguage() || 'en', { style: 'long', type: 'conjunction', - }).format(items); - - let lastIdx = 0; - let lastLen = 0; + }).formatToParts(items); return (
<> - {items.map((item) => { - const currentIdx = formattedString.indexOf(item); - const element = ( - - {formattedString.slice(lastIdx + lastLen, currentIdx)} - - + {parts.map((part, partIndex) => { + // Literal parts are separators/conjunctions (e.g. ", " or " and ") — render as text + if (part.type === 'literal') { + // eslint-disable-next-line react/no-array-index-key -- index is the only stable key for literal separator parts + return {part.value}; + } + // Element parts correspond to each item — render as clickable buttons + return ( + // eslint-disable-next-line react/no-array-index-key -- index is the only stable key for element parts with potential duplicates + ); - - lastIdx = currentIdx; - lastLen = item.length; - - return element; })}
diff --git a/frontend/packages/console-shared/src/components/progressive-list/__tests__/ProgressiveListFooter.spec.tsx b/frontend/packages/console-shared/src/components/progressive-list/__tests__/ProgressiveListFooter.spec.tsx index 7ce4b20d941..c277316c88a 100644 --- a/frontend/packages/console-shared/src/components/progressive-list/__tests__/ProgressiveListFooter.spec.tsx +++ b/frontend/packages/console-shared/src/components/progressive-list/__tests__/ProgressiveListFooter.spec.tsx @@ -40,4 +40,43 @@ describe('ProgressiveListFooter', () => { const buttons = screen.getAllByRole('button'); expect(buttons).toHaveLength(3); }); + + it('should render duplicate item labels with correct conjunction text', () => { + const { container } = renderWithProviders( + {}} />, + ); + + expect(container.textContent).toBe( + 'Click on the names to access advanced options for Foo and Foo.', + ); + const buttons = screen.getAllByRole('button'); + expect(buttons).toHaveLength(2); + expect(buttons[0]).toHaveTextContent('Foo'); + expect(buttons[1]).toHaveTextContent('Foo'); + }); + + it('should render items matching the conjunction literal correctly', () => { + const { container } = renderWithProviders( + {}} />, + ); + + expect(container.textContent).toBe( + 'Click on the names to access advanced options for Foo and and.', + ); + const buttons = screen.getAllByRole('button'); + expect(buttons).toHaveLength(2); + expect(buttons[0]).toHaveTextContent('Foo'); + expect(buttons[1]).toHaveTextContent('and'); + }); + + it('should call onShowItem with the correct item for duplicate labels', () => { + const onShowItem = jest.fn(); + renderWithProviders( + , + ); + + const buttons = screen.getAllByRole('button'); + buttons[1].click(); + expect(onShowItem).toHaveBeenCalledWith('Foo'); + }); }); diff --git a/frontend/packages/dev-console/src/components/deployments/deployment-strategy/advanced-options/LifecycleHookField.tsx b/frontend/packages/dev-console/src/components/deployments/deployment-strategy/advanced-options/LifecycleHookField.tsx index 8ada000313f..fa8c22bbd37 100644 --- a/frontend/packages/dev-console/src/components/deployments/deployment-strategy/advanced-options/LifecycleHookField.tsx +++ b/frontend/packages/dev-console/src/components/deployments/deployment-strategy/advanced-options/LifecycleHookField.tsx @@ -53,8 +53,10 @@ const LifecycleHookField: FC = ({ `formData.deploymentStrategy.${dataAttribute}.${lifecycleHookName}.isAddingLch`, false, ); + // eslint-disable-next-line react-hooks/immutability -- intentional Formik initialValues sync initialValues.formData.deploymentStrategy[dataAttribute][lifecycleHookName] = deploymentStrategy[dataAttribute][lifecycleHookName]; + // eslint-disable-next-line react-hooks/immutability -- intentional Formik initialValues sync initialValues.formData.deploymentStrategy.imageStreamData[lifecycleHookName] = deploymentStrategy.imageStreamData[lifecycleHookName]; }, [ @@ -75,8 +77,10 @@ const LifecycleHookField: FC = ({ resNamespace, resourceType, ); + // eslint-disable-next-line react-hooks/immutability -- intentional Formik initialValues sync initialValues.formData.deploymentStrategy[dataAttribute][lifecycleHookName] = data[dataAttribute][lifecycleHookName]; + // eslint-disable-next-line react-hooks/immutability -- intentional Formik initialValues sync initialValues.formData.deploymentStrategy.imageStreamData[lifecycleHookName] = data.imageStreamData[lifecycleHookName]; setFieldValue( diff --git a/frontend/packages/dev-console/src/components/pipelines-visualization/PipelineTaskNode.tsx b/frontend/packages/dev-console/src/components/pipelines-visualization/PipelineTaskNode.tsx index c7e0d2b98f0..a1b1221f8b7 100644 --- a/frontend/packages/dev-console/src/components/pipelines-visualization/PipelineTaskNode.tsx +++ b/frontend/packages/dev-console/src/components/pipelines-visualization/PipelineTaskNode.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react'; -import { useRef, useMemo, memo } from 'react'; +import { useRef, memo } from 'react'; import { Tooltip } from '@patternfly/react-core'; import { css } from '@patternfly/react-styles'; import type { Node, WithContextMenuProps, WithSelectionProps } from '@patternfly/react-topology'; @@ -123,15 +123,9 @@ const PipelineTaskNode: FC = ({ ? `${succeededStepsCount}/${stepStatusList.length}` : null; - const passedData = useMemo(() => { - const newData = { ...data }; - Object.keys(newData).forEach((key) => { - if (newData[key] === undefined) { - delete newData[key]; - } - }); - return newData; - }, [data]); + const passedData = Object.fromEntries( + Object.entries(data).filter(([, value]) => value !== undefined), + ); const hasTaskIcon = !!(data.taskIconClass || data.taskIcon); const tooltipContent = getTooltipContent(data.task?.status?.reason); diff --git a/frontend/packages/dev-console/src/components/pipelines-visualization/PipelineVisualizationSurface.tsx b/frontend/packages/dev-console/src/components/pipelines-visualization/PipelineVisualizationSurface.tsx index c51953987aa..f30eb14fcdb 100644 --- a/frontend/packages/dev-console/src/components/pipelines-visualization/PipelineVisualizationSurface.tsx +++ b/frontend/packages/dev-console/src/components/pipelines-visualization/PipelineVisualizationSurface.tsx @@ -96,24 +96,21 @@ const PipelineVisualizationSurface: FC = ({ }); setVis(visualization); } else { - const graph = storedGraphModel.current; - if (graph) { - model.graph = graph; - } - vis.fromModel(model); + const localModel = storedGraphModel.current + ? { ...model, graph: storedGraphModel.current } + : model; + vis.fromModel(localModel); vis.getGraph().layout(); } }, [vis, model, onLayoutUpdate, componentFactory]); useEffect(() => { if (model && vis) { - const graph = storedGraphModel.current; - if (graph) { - model.graph = graph; - } - vis.fromModel(model); + const localModel = storedGraphModel.current + ? { ...model, graph: storedGraphModel.current } + : model; + vis.fromModel(localModel); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [model, vis]); if (!vis) return null; diff --git a/frontend/packages/dev-console/src/components/resource-quota/ResourceQuotaAlert.tsx b/frontend/packages/dev-console/src/components/resource-quota/ResourceQuotaAlert.tsx index a49fae4ad5e..7e14b35a83a 100644 --- a/frontend/packages/dev-console/src/components/resource-quota/ResourceQuotaAlert.tsx +++ b/frontend/packages/dev-console/src/components/resource-quota/ResourceQuotaAlert.tsx @@ -68,11 +68,9 @@ export const ResourceQuotaAlert: FC = ({ namespace }) = [appliedclusterresourcequotas], ); - let totalResourcesAtQuota = useMemo( - () => [...totalRQatQuota, ...totalACRQatQuota], - [totalRQatQuota, totalACRQatQuota], + const totalResourcesAtQuota = [...totalRQatQuota, ...totalACRQatQuota].filter( + (resourceAtQuota) => resourceAtQuota !== 0, ); - totalResourcesAtQuota = totalResourcesAtQuota.filter((resourceAtQuota) => resourceAtQuota !== 0); useEffect(() => { if (totalResourcesAtQuota.length === 1) { diff --git a/frontend/packages/knative-plugin/src/topology/components/nodes/EventSink.tsx b/frontend/packages/knative-plugin/src/topology/components/nodes/EventSink.tsx index 78645bbc5e0..c4b4df5b24f 100644 --- a/frontend/packages/knative-plugin/src/topology/components/nodes/EventSink.tsx +++ b/frontend/packages/knative-plugin/src/topology/components/nodes/EventSink.tsx @@ -1,5 +1,5 @@ import type { ReactNode, FC } from 'react'; -import { useRef, useMemo } from 'react'; +import { useRef } from 'react'; import { Tooltip } from '@patternfly/react-core'; import type { Node, @@ -75,10 +75,7 @@ const EventSink: FC = ({ element.getSourceEdges()?.filter((edge: Edge) => edge.getType() === TYPE_KAFKA_CONNECTION_LINK) .length > 0; const { revisions, associatedDeployment } = resources; - const revisionIds = useMemo( - () => revisions?.map((revision) => revision.metadata.uid), - [revisions], - ); + const revisionIds = revisions?.map((revision) => revision.metadata.uid); const { loaded, loadError, pods } = usePodsForRevisions(revisionIds, resource.metadata.namespace); const controller = useVisualizationController(); @@ -97,7 +94,7 @@ const EventSink: FC = ({ const isKafkaSink = referenceFor(resource) === referenceForModel(KafkaSinkModel); - const donutStatus = useMemo(() => { + const donutStatus = (() => { if (!revisionIds && loadedDeployment && !loadErrorDeployment) { return podsDeployment; } @@ -113,16 +110,7 @@ const EventSink: FC = ({ }; } return null; - }, [ - revisionIds, - loadedDeployment, - loadErrorDeployment, - loaded, - loadError, - podsDeployment, - pods, - resource, - ]); + })(); return ( = (pro const providedAPIs = providedAPIsForCSV(props.obj); const marketplaceSupportWorkflow = metadata?.annotations?.[OLMAnnotation.SupportWorkflow] || ''; const initializationLink = getInitializationLink(metadata?.annotations); - const initializationResource = useMemo( - () => - !initializationLink && - getInitializationResource(metadata?.annotations, { - onError: (error) => { - // eslint-disable-next-line no-console - console.error('Error while parsing CSV initialization resource JSON,', error.message); - }, - }), - [metadata?.annotations, initializationLink], - ); + const initializationResource = + !initializationLink && + getInitializationResource(metadata?.annotations, { + onError: (error) => { + // eslint-disable-next-line no-console + console.error('Error while parsing CSV initialization resource JSON,', error.message); + }, + }); const supportWorkflowUrl = useMemo(() => { if (marketplaceSupportWorkflow) { diff --git a/frontend/packages/operator-lifecycle-manager/src/components/k8s-resource.tsx b/frontend/packages/operator-lifecycle-manager/src/components/k8s-resource.tsx index 6dfc7822ccb..986f9df88fc 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/k8s-resource.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/k8s-resource.tsx @@ -1,5 +1,4 @@ import type { FC } from 'react'; -import { useMemo } from 'react'; import { sortable } from '@patternfly/react-table'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; @@ -166,13 +165,10 @@ export const Resources: FC = (props) => { }, ); - const customData = useMemo( - () => ({ - linkFor: linkForCsvResource, - providedAPI, - }), - [providedAPI], - ); + const customData = { + linkFor: linkForCsvResource, + providedAPI, + }; return ( = (props) => { }); const csvPlugins = getClusterServiceVersionPlugins(currentCSVDesc?.annotations); - const initializationResourceReference = useMemo( - () => (initializationResource ? referenceFor(initializationResource) : null), - [initializationResource], - ); + const initializationResourceReference = initializationResource + ? referenceFor(initializationResource) + : null; const globalNS = (props.operatorGroup?.data || ([] as OperatorGroupKind[])).find( @@ -332,14 +331,11 @@ const OperatorHubSubscribeForm: FC = (props) => { ); const supportsGlobal = globalInstallMode && globalInstallMode.supported; - const navigateToInstallPage = useCallback( - (csvName: string) => { - navigate( - `/operatorhub/install/${catalogNamespace}/${catalog}/${pkg}/${csvName}/to/${selectedTargetNamespace}`, - ); - }, - [catalog, catalogNamespace, navigate, pkg, selectedTargetNamespace], - ); + const navigateToInstallPage = (csvName: string) => { + navigate( + `/operatorhub/install/${catalogNamespace}/${catalog}/${pkg}/${csvName}/to/${selectedTargetNamespace}`, + ); + }; if (!supportsSingle && !supportsGlobal) { return ( diff --git a/frontend/packages/topology/src/components/graph-view/Topology.tsx b/frontend/packages/topology/src/components/graph-view/Topology.tsx index c45b88e06fe..45679a362bb 100644 --- a/frontend/packages/topology/src/components/graph-view/Topology.tsx +++ b/frontend/packages/topology/src/components/graph-view/Topology.tsx @@ -213,10 +213,16 @@ const TopologyComponent: FC< useEffect(() => { if (model && visualizationReady) { + const localModel = { + ...model, + graph: { ...model.graph }, + nodes: model.nodes.map((n) => ({ ...n })), + edges: model.edges.map((e) => ({ ...e })), + }; if (!storedLayoutApplied.current) { const storedGraphModel = getStoredGraphModel(namespace); if (storedGraphModel) { - model.graph = { + localModel.graph = { ...graphModel.graph, x: storedGraphModel.x, y: storedGraphModel.y, @@ -227,7 +233,7 @@ const TopologyComponent: FC< } const storedLayout = topologyLayoutDataJson?.[namespace]; if (storedLayout) { - model.nodes.forEach((n) => { + localModel.nodes.forEach((n) => { const storedNode = storedLayout.nodes.find((sn) => sn.id === n.id); if (storedNode) { STORED_NODE_LAYOUT_FIELDS.forEach((key) => { @@ -238,20 +244,20 @@ const TopologyComponent: FC< } } - model.nodes.forEach((n) => { + localModel.nodes.forEach((n) => { const oldNode = visualization.getNodeById(n.id); if (oldNode && _.isEqual(oldNode.getData(), n.data)) { n.data = oldNode.getData(); } }); - model.edges.forEach((e) => { + localModel.edges.forEach((e) => { const oldEdge = visualization.getEdgeById(e.id); if (oldEdge && _.isEqual(oldEdge.getData(), e.data)) { e.data = oldEdge.getData(); } }); - visualization.fromModel(model); + visualization.fromModel(localModel); // Make sure something is visible in the case where stored locations are off the screen if (!storedLayoutApplied.current) { diff --git a/frontend/packages/topology/src/components/side-bar/providers/SideBarTabHookResolver.tsx b/frontend/packages/topology/src/components/side-bar/providers/SideBarTabHookResolver.tsx index f47851fab69..169bdef31a9 100644 --- a/frontend/packages/topology/src/components/side-bar/providers/SideBarTabHookResolver.tsx +++ b/frontend/packages/topology/src/components/side-bar/providers/SideBarTabHookResolver.tsx @@ -51,6 +51,7 @@ const TabBarTabHookResolver: FC = ({ [contentElement] = hookResult; } else if (section) { if (!blamedDeprecatedPlugins[rest.id]) { + // eslint-disable-next-line react-hooks/immutability -- intentional warn-once cache blamedDeprecatedPlugins[rest.id] = true; // eslint-disable-next-line no-console console.warn( @@ -99,16 +100,20 @@ const TabBarTabHookResolver: FC = ({ // show default side bar if (tabsLoaded && tabs.length === 0) { const resource = getResource(graphElement); - resource && - tabs.push({ + const defaultTabs: Tab[] = []; + if (resource) { + defaultTabs.push({ name: t('Details'), component: () => , }); - isEdge(graphElement) && - tabs.push({ + } + if (isEdge(graphElement)) { + defaultTabs.push({ name: t('Resources'), component: () => , }); + } + return children(defaultTabs, tabsLoaded); } return children(tabs, tabsLoaded); diff --git a/frontend/packages/topology/src/data-transforms/TopologyDataRetriever.tsx b/frontend/packages/topology/src/data-transforms/TopologyDataRetriever.tsx index 76a84303114..ac20188ccbb 100644 --- a/frontend/packages/topology/src/data-transforms/TopologyDataRetriever.tsx +++ b/frontend/packages/topology/src/data-transforms/TopologyDataRetriever.tsx @@ -34,17 +34,20 @@ const TopologyDataRetriever: FC = ({ trafficData }) // Wipe the current model on a namespace change useEffect(() => { + // eslint-disable-next-line react-hooks/immutability -- intentional context model reset on namespace change dataModelContext.model = null; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [namespace]); + }, [namespace, dataModelContext]); useEffect(() => { if (!_.isEmpty(resources)) { updateTopologyDataModel(dataModelContext, resources, trafficData, monitoringAlerts) .then((res) => { + // eslint-disable-next-line react-hooks/immutability -- intentional context state update dataModelContext.loadError = res.loadError; if (res.loaded) { + // eslint-disable-next-line react-hooks/immutability -- intentional context state update dataModelContext.loaded = true; + // eslint-disable-next-line react-hooks/immutability -- intentional context state update dataModelContext.model = res.model; } }) diff --git a/frontend/public/components/edit-yaml.tsx b/frontend/public/components/edit-yaml.tsx index 904ccc0a866..0da5c073eb6 100644 --- a/frontend/public/components/edit-yaml.tsx +++ b/frontend/public/components/edit-yaml.tsx @@ -190,10 +190,7 @@ const EditYAMLInner: FC = (props) => { const launchModal = useOverlay(); const [templateExtensions, resolvedTemplates] = useResolvedExtensions( - useCallback( - (e): e is YAMLTemplate => isYAMLTemplate(e) && e.properties.model.kind === props?.obj?.kind, - [props?.obj?.kind], - ), + (e): e is YAMLTemplate => isYAMLTemplate(e) && e.properties.model.kind === props?.obj?.kind, ); const { theme, fontSize, showTooltips, stickyScrollEnabled } = useEditYamlSettings(); diff --git a/frontend/public/components/modals/cluster-update-modal.tsx b/frontend/public/components/modals/cluster-update-modal.tsx index 430a8841bf2..289bd42756f 100644 --- a/frontend/public/components/modals/cluster-update-modal.tsx +++ b/frontend/public/components/modals/cluster-update-modal.tsx @@ -1,5 +1,5 @@ import type { FormEvent, FormEventHandler } from 'react'; -import { useState, useEffect, useCallback, Fragment } from 'react'; +import { useState, useEffect, Fragment } from 'react'; import { Alert, Button, @@ -114,67 +114,52 @@ const ClusterUpdateModal = (props: ClusterUpdateModalProps) => { const desiredNotRecommendedUpdateConditions = getNotRecommendedUpdateCondition( desiredNotRecommendedUpdate?.conditions, ); - const submit: FormEventHandler = useCallback( - (e) => { - e.preventDefault(); - if (!desiredRecommendedUpdate && !desiredNotRecommendedUpdate) { - setError( - t( - 'Version {{desiredVersion}} not found among the supported updates. Select another version.', - { desiredVersion }, - ), - ); - return; - } + const submit: FormEventHandler = (e) => { + e.preventDefault(); + if (!desiredRecommendedUpdate && !desiredNotRecommendedUpdate) { + setError( + t( + 'Version {{desiredVersion}} not found among the supported updates. Select another version.', + { desiredVersion }, + ), + ); + return; + } - // Clear any previous error message. - setError(''); - let MCPsToPausePromises; - let MCPsToResumePromises; - if (upgradeType === UpgradeTypes.Full) { - MCPsToPausePromises = []; - MCPsToResumePromises = getMCPsToPausePromises(pausedMCPs, false); - } else { - const MCPsToPause = pauseableMCPs.filter((mcp) => - machineConfigPoolsToPause.find((m) => m === mcp.metadata.name), - ); - const MCPsToResume = pauseableMCPs.filter((mcp) => !MCPsToPause.includes(mcp)); - MCPsToPausePromises = getMCPsToPausePromises(MCPsToPause, true); - MCPsToResumePromises = getMCPsToPausePromises(MCPsToResume, false); - } - const patch = [ - { - op: 'add', - path: '/spec/desiredUpdate', - value: desiredNotRecommendedUpdate - ? desiredNotRecommendedUpdate.release - : desiredRecommendedUpdate, - }, - ]; - handlePromise( - Promise.all([ - k8sPatch(ClusterVersionModel, cv, patch), - ...MCPsToResumePromises, - ...MCPsToPausePromises, - ]), - ) - .then(() => close()) - .catch(() => {}); - }, - [ - desiredRecommendedUpdate, - desiredNotRecommendedUpdate, - t, - desiredVersion, - upgradeType, - pausedMCPs, - pauseableMCPs, - machineConfigPoolsToPause, - handlePromise, - cv, - close, - ], - ); + // Clear any previous error message. + setError(''); + let MCPsToPausePromises; + let MCPsToResumePromises; + if (upgradeType === UpgradeTypes.Full) { + MCPsToPausePromises = []; + MCPsToResumePromises = getMCPsToPausePromises(pausedMCPs, false); + } else { + const MCPsToPause = pauseableMCPs.filter((mcp) => + machineConfigPoolsToPause.find((m) => m === mcp.metadata.name), + ); + const MCPsToResume = pauseableMCPs.filter((mcp) => !MCPsToPause.includes(mcp)); + MCPsToPausePromises = getMCPsToPausePromises(MCPsToPause, true); + MCPsToResumePromises = getMCPsToPausePromises(MCPsToResume, false); + } + const patch = [ + { + op: 'add', + path: '/spec/desiredUpdate', + value: desiredNotRecommendedUpdate + ? desiredNotRecommendedUpdate.release + : desiredRecommendedUpdate, + }, + ]; + handlePromise( + Promise.all([ + k8sPatch(ClusterVersionModel, cv, patch), + ...MCPsToResumePromises, + ...MCPsToPausePromises, + ]), + ) + .then(() => close()) + .catch(() => {}); + }; const dropdownItem = (version) => { const isDisabled = clusterUpgradeableFalse && isMinorVersionNewer(currentVersion, version); return { diff --git a/frontend/public/components/pod-connect.tsx b/frontend/public/components/pod-connect.tsx index 1d84bed9029..43f8ac9184c 100644 --- a/frontend/public/components/pod-connect.tsx +++ b/frontend/public/components/pod-connect.tsx @@ -90,11 +90,9 @@ export const PodConnect: FC = ({ const containers = useMemo(() => _.keyBy(_.get(obj, 'spec.containers', []), 'name'), [obj]); - const podName = useMemo(() => obj.metadata?.name || '', [obj?.metadata?.name]); - const namespace = useMemo(() => obj.metadata?.namespace || 'default', [obj?.metadata?.namespace]); - // We are being more specific with the dependency array here to avoid additional rerenders when other fields in obj changes - // eslint-disable-next-line react-hooks/exhaustive-deps - const isWindows = useMemo(() => isWindowsPod(obj), [obj?.spec?.tolerations]); + const podName = obj.metadata?.name || ''; + const namespace = obj.metadata?.namespace || 'default'; + const isWindows = isWindowsPod(obj); const connect = useCallback(() => { const usedClient = isOpenShift ? 'oc' : 'kubectl'; diff --git a/frontend/public/components/utils/resource-icon.tsx b/frontend/public/components/utils/resource-icon.tsx index c91f2503e49..b2ee5caea25 100644 --- a/frontend/public/components/utils/resource-icon.tsx +++ b/frontend/public/components/utils/resource-icon.tsx @@ -7,25 +7,19 @@ import type { K8sResourceKindReference } from '../../module/k8s'; import { kindToAbbr } from '../../module/k8s/get-resources'; import { modelFor } from '../../module/k8s/k8s-models'; -const MEMO = {}; - export const ResourceIcon: FC = ({ className, groupVersionKind, kind }) => { // if no kind or groupVersionKind, return null so an empty icon isn't rendered if (!kind && !groupVersionKind) { return null; } const kindReference = kind || getReference(groupVersionKind); - const memoKey = className ? `${kindReference}/${className}` : kindReference; - if (MEMO[memoKey]) { - return MEMO[memoKey]; - } const kindObj = modelFor(kindReference); const kindStr = kindObj?.kind ?? kindReference; const backgroundColor = _.get(kindObj, 'color', undefined); const klass = css(`co-m-resource-icon co-m-resource-${kindStr.toLowerCase()}`, className); const iconLabel = (kindObj && kindObj.abbr) || kindToAbbr(kindStr); - const rendered = ( + return ( <> {kindStr} @@ -33,11 +27,6 @@ export const ResourceIcon: FC = ({ className, groupVersionKin ); - if (kindObj) { - MEMO[memoKey] = rendered; - } - - return rendered; }; export type ResourceNameProps = {