Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,10 +474,7 @@ const useConsolePluginRows = (enabledPlugins: string[]) => {
};

const PluginsPage: FC<ConsoleOperatorConfigPageProps> = (props) => {
const enabledPlugins = useMemo(
() => props?.obj?.spec?.plugins ?? [],
[props?.obj?.spec?.plugins],
);
const enabledPlugins = props?.obj?.spec?.plugins ?? [];
const { rows, loaded } = useConsolePluginRows(enabledPlugins);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const PersistentVolumeRow: FC<PersistentVolumeRowProps> = ({ persistentVolumeDat
),
);
return podsForPVC ? getCurrentPod(podsForPVC) : undefined;
}, [persistentVolumeData.vmi, persistentVolumeData.persistentVolumeClaim?.metadata.name, pods]);
}, [persistentVolumeData, pods]);

return (
<tr className="pf-v6-c-table__tr">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import type { FC } from 'react';
import { Fragment } from 'react';

const MEMO = {};

const CamelCaseWrap: FC<CamelCaseWrapProps> = ({ 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 (
<span data-test={dataTest}>
{words.map((word, i) => (
// eslint-disable-next-line react/no-array-index-key
Expand All @@ -25,8 +19,6 @@ const CamelCaseWrap: FC<CamelCaseWrapProps> = ({ value, dataTest }) => {
))}
</span>
);
MEMO[value] = rendered;
return rendered;
};

type CamelCaseWrapProps = {
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -31,18 +30,15 @@ export const NumberSpinnerField: FC<NumberSpinnerFieldProps> = ({

useFormikValidationFix(field.value);

const handleChange: ReactEventHandler<HTMLInputElement> = 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<HTMLInputElement> = (event) => {
field.onChange(event);
setFieldValue(
props.name,
props?.setOutputAsIntegerFlag
? _.toInteger(event.currentTarget.value)
: event.currentTarget.value,
);
};

return (
<FormGroup fieldId={fieldId} label={label} isRequired={required}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,27 @@ export const ProgressiveListFooter: FC<ProgressiveListFooterProps> = ({
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 (
<Footer>
<>
{items.map((item) => {
const currentIdx = formattedString.indexOf(item);
const element = (
<Fragment key={item}>
{formattedString.slice(lastIdx + lastLen, currentIdx)}
<Button variant="link" isInline onClick={() => onShowItem(item)}>
{item}
</Button>
</Fragment>
{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 <Fragment key={partIndex}>{part.value}</Fragment>;
}
// 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
<Button key={partIndex} variant="link" isInline onClick={() => onShowItem(part.value)}>
{part.value}
</Button>
);

lastIdx = currentIdx;
lastLen = item.length;

return element;
})}
</>
</Footer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<ProgressiveListFooter Footer={Footer} items={['Foo', 'Foo']} onShowItem={() => {}} />,
);

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(
<ProgressiveListFooter Footer={Footer} items={['Foo', 'and']} onShowItem={() => {}} />,
);

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(
<ProgressiveListFooter Footer={Footer} items={['Foo', 'Foo']} onShowItem={onShowItem} />,
);

const buttons = screen.getAllByRole('button');
buttons[1].click();
expect(onShowItem).toHaveBeenCalledWith('Foo');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ const LifecycleHookField: FC<LifecycleHookFieldProps> = ({
`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];
}, [
Expand All @@ -75,8 +77,10 @@ const LifecycleHookField: FC<LifecycleHookFieldProps> = ({
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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -123,15 +123,9 @@ const PipelineTaskNode: FC<PipelineTaskNodeProps> = ({
? `${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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,24 +96,21 @@ const PipelineVisualizationSurface: FC<PipelineVisualizationSurfaceProps> = ({
});
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,9 @@ export const ResourceQuotaAlert: FC<ResourceQuotaAlertProps> = ({ 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -75,10 +75,7 @@ const EventSink: FC<EventSinkProps> = ({
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();
Expand All @@ -97,7 +94,7 @@ const EventSink: FC<EventSinkProps> = ({

const isKafkaSink = referenceFor(resource) === referenceForModel(KafkaSinkModel);

const donutStatus = useMemo(() => {
const donutStatus = (() => {
if (!revisionIds && loadedDeployment && !loadErrorDeployment) {
return podsDeployment;
}
Expand All @@ -113,16 +110,7 @@ const EventSink: FC<EventSinkProps> = ({
};
}
return null;
}, [
revisionIds,
loadedDeployment,
loadErrorDeployment,
loaded,
loadError,
podsDeployment,
pods,
resource,
]);
})();

return (
<Tooltip
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1155,17 +1155,14 @@ const ClusterServiceVersionDetails: FC<ClusterServiceVersionDetailsProps> = (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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -166,13 +165,10 @@ export const Resources: FC<ResourcesProps> = (props) => {
},
);

const customData = useMemo(
() => ({
linkFor: linkForCsvResource,
providedAPI,
}),
[providedAPI],
);
const customData = {
linkFor: linkForCsvResource,
providedAPI,
};

return (
<MultiListPage
Expand Down
Loading