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
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import {
LazyAnnotationsModalOverlay,
LazyDeleteModalOverlay,
LazyLabelsModalOverlay,
LazyTaintsModalOverlay,
LazyTolerationsModalOverlay,
} from '@console/internal/components/modals';
import { useConfigureCountModal } from '@console/internal/components/modals/configure-count-modal';
import { TaintsModalOverlay } from '@console/internal/components/modals/taints-modal';
import { asAccessReview } from '@console/internal/components/utils/rbac';
import { resourceObjPath } from '@console/internal/components/utils/resource-link';
import type { K8sModel, K8sResourceKind, NodeKind } from '@console/internal/module/k8s';
Expand Down Expand Up @@ -143,7 +143,7 @@ export const useCommonActions = <T extends readonly CommonActionCreator[]>(
id: 'edit-taints',
label: t('console-app~Edit taints'),
cta: () =>
launchModal(TaintsModalOverlay, {
launchModal(LazyTaintsModalOverlay, {
resourceKind: kind,
resource: resource as NodeKind,
}),
Expand Down
20 changes: 15 additions & 5 deletions frontend/public/components/cluster-settings/cluster-settings.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import type { FC, ReactNode } from 'react';
import { useEffect } from 'react';
import { useEffect, useRef, useMemo } from 'react';
import * as _ from 'lodash';
import { css } from '@patternfly/react-styles';
import * as semver from 'semver';
Expand Down Expand Up @@ -888,7 +888,7 @@ export const ClusterVersionDetailsTable: FC<ClusterVersionDetailsTableProps> = (
obj: cv,
autoscalers,
}) => {
const { removeQueryArgument } = useQueryParamsMutator();
const { getQueryArgument, removeQueryArgument } = useQueryParamsMutator();
const { history = [] } = cv.status;
const clusterID = getClusterID(cv);
const desiredImage: string = _.get(cv, 'status.desired.image') || '';
Expand All @@ -908,16 +908,26 @@ export const ClusterVersionDetailsTable: FC<ClusterVersionDetailsTableProps> = (
const updateStartedTime = getStartedTimeForCVDesiredVersion(cv, desiredVersion);
const workerMachineConfigPool = getMCPByName(machineConfigPools, NodeTypes.worker);
const launchModal = useOverlay();
const modalOpenedRef = useRef(false);

// Check URL params once to avoid re-reading on every cv change
const hasShowVersions = useMemo(() => !!getQueryArgument('showVersions'), [getQueryArgument]);
const hasShowChannels = useMemo(() => !!getQueryArgument('showChannels'), [getQueryArgument]);

useEffect(() => {
if (new URLSearchParams(window.location.search).has('showVersions')) {
if (modalOpenedRef.current) {
return;
}
if (hasShowVersions) {
launchModal(LazyClusterUpdateModalOverlay, { cv });
removeQueryArgument('showVersions');
} else if (new URLSearchParams(window.location.search).has('showChannels')) {
modalOpenedRef.current = true;
} else if (hasShowChannels) {
launchModal(LazyClusterChannelModalOverlay, { cv });
removeQueryArgument('showChannels');
modalOpenedRef.current = true;
}
}, [launchModal, cv, removeQueryArgument]);
}, [launchModal, cv, removeQueryArgument, hasShowVersions, hasShowChannels]);

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ describe('ColumnManagementModal component', () => {
}, []),
),
type: columnManagementType,
showNamespaceOverride: true,
}}
userSettingState={null}
setUserSettingState={jest.fn()}
Expand Down Expand Up @@ -205,6 +206,7 @@ describe('ColumnManagementModal component', () => {
id: columnManagementID,
selectedColumns: new Set(modifiedColumns),
type: columnManagementType,
showNamespaceOverride: true,
}}
userSettingState={null}
setUserSettingState={jest.fn()}
Expand Down
168 changes: 100 additions & 68 deletions frontend/public/components/modals/cluster-channel-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,39 @@
import type { FormEventHandler } from 'react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Content, TextInput, ContentVariants } from '@patternfly/react-core';
import {
Button,
Content,
ContentVariants,
Form,
FormGroup,
FormHelperText,
HelperText,
HelperTextItem,
Modal,
ModalBody,
ModalHeader,
ModalVariant,
TextInput,
} from '@patternfly/react-core';
import * as semver from 'semver';

import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider';
import { ChannelDocLink } from '../cluster-settings/cluster-settings';
import { ClusterVersionModel } from '../../models';
import { ConsoleSelect } from '@console/internal/components/utils/console-select';
import { isManaged } from '../utils/documentation';
import {
ModalBody,
ModalComponentProps,
ModalSubmitFooter,
ModalTitle,
ModalWrapper,
} from '../factory/modal';
import { ModalComponentProps } from '../factory/modal';
import {
ClusterVersionKind,
getAvailableClusterChannels,
getLastCompletedUpdate,
k8sPatch,
} from '../../module/k8s';
import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler';
import { ModalFooterWithAlerts } from '@console/shared/src/components/modals/ModalFooterWithAlerts';

const ClusterChannelModal = (props: ClusterChannelModalProps) => {
export const ClusterChannelModal = (props: ClusterChannelModalProps) => {
const { cancel, close, cv } = props;
const [handlePromise, inProgress, errorMessage] = usePromiseHandler();
const [channel, setChannel] = useState(cv.spec.channel);
Expand All @@ -34,6 +43,8 @@ const ClusterChannelModal = (props: ClusterChannelModalProps) => {
return o;
}, {});
const version = semver.parse(getLastCompletedUpdate(cv));
const versionMajor = version?.major ?? 4;
const versionMinor = version?.minor ?? 0;
const channelsExist = cv.status?.desired?.channels?.length;
const submit: FormEventHandler<HTMLFormElement> = (e): void => {
e.preventDefault();
Expand All @@ -44,76 +55,97 @@ const ClusterChannelModal = (props: ClusterChannelModalProps) => {
};

return (
<form onSubmit={submit} name="form" className="modal-content" data-test="channel-modal">
<ModalTitle>
{channelsExist ? t('public~Select channel') : t('public~Input channel')}
</ModalTitle>
<>
<ModalHeader
title={channelsExist ? t('public~Select channel') : t('public~Input channel')}
data-test-id="modal-title"
labelId="cluster-channel-modal-title"
/>
<ModalBody>
<Content>
<Content component={ContentVariants.p}>
{channelsExist
? t(
'public~The current version is available in the channels listed in the dropdown below. Select a channel that reflects the desired version. Critical security updates will be delivered to any vulnerable channels.',
)
: t(
'public~Input a channel that reflects the desired version. To verify if the version exists in a channel, save and check the update status. Critical security updates will be delivered to any vulnerable channels.',
)}
</Content>
{!isManaged() && (
<Content component={ContentVariants.p} className="pf-v6-u-mb-md">
<ChannelDocLink />
<Form id="cluster-channel-form" onSubmit={submit}>
<Content>
<Content component={ContentVariants.p}>
{channelsExist
? t(
'public~The current version is available in the channels listed in the dropdown below. Select a channel that reflects the desired version. Critical security updates will be delivered to any vulnerable channels.',
)
: t(
'public~Input a channel that reflects the desired version. To verify if the version exists in a channel, save and check the update status. Critical security updates will be delivered to any vulnerable channels.',
)}
</Content>
)}
</Content>
<div className="form-group">
<label htmlFor="channel">{t('public~Channel')}</label>
{channelsExist ? (
<ConsoleSelect
className="cluster-channel-modal__dropdown"
id="channel"
items={availableChannels}
onChange={(newChannel: string) => setChannel(newChannel)}
selectedKey={channel}
title={t('public~Channel')}
/>
) : (
<>
<TextInput
{!isManaged() && (
<Content component={ContentVariants.p}>
<ChannelDocLink />
</Content>
)}
</Content>
<FormGroup label={t('public~Channel')} fieldId="channel">
{channelsExist ? (
<ConsoleSelect
className="cluster-channel-modal__dropdown"
id="channel"
onChange={(_event, newChannel) => setChannel(newChannel)}
value={channel}
placeholder={t(`public~e.g., {{version}}`, {
version: `stable-${version.major}.${version.minor}`,
})}
data-test="channel-modal-input"
items={availableChannels}
onChange={(newChannel: string) => setChannel(newChannel)}
selectedKey={channel}
title={t('public~Channel')}
/>
<p className="help-block">
{t(`public~Potential channels are {{stable}}, {{fast}}, or {{candidate}}.`, {
stable: `stable-${version.major}.${version.minor}`,
fast: `fast-${version.major}.${version.minor}`,
candidate: `candidate-${version.major}.${version.minor}`,
})}
</p>
</>
)}
</div>
) : (
<>
<TextInput
id="channel"
onChange={(_event, newChannel) => setChannel(newChannel)}
value={channel}
placeholder={t(`public~e.g., {{version}}`, {
version: `stable-${versionMajor}.${versionMinor}`,
})}
data-test="channel-modal-input"
/>
<FormHelperText>
<HelperText>
<HelperTextItem>
{t(`public~Potential channels are {{stable}}, {{fast}}, or {{candidate}}.`, {
stable: `stable-${versionMajor}.${versionMinor}`,
fast: `fast-${versionMajor}.${versionMinor}`,
candidate: `candidate-${versionMajor}.${versionMinor}`,
})}
</HelperTextItem>
</HelperText>
</FormHelperText>
</>
)}
</FormGroup>
</Form>
</ModalBody>
<ModalSubmitFooter
errorMessage={errorMessage}
inProgress={inProgress}
submitText={t('public~Save')}
cancelText={t('public~Cancel')}
cancel={cancel}
/>
</form>
<ModalFooterWithAlerts errorMessage={errorMessage}>
<Button
type="submit"
variant="primary"
isLoading={inProgress}
form="cluster-channel-form"
data-test="confirm-action"
id="confirm-action"
>
{t('public~Save')}
</Button>
<Button variant="link" onClick={cancel} type="button" data-test-id="modal-cancel-action">
{t('public~Cancel')}
</Button>
</ModalFooterWithAlerts>
</>
);
};

export const ClusterChannelModalOverlay: OverlayComponent<ClusterChannelModalProps> = (props) => {
return (
<ModalWrapper blocking onClose={props.closeOverlay}>
<Modal
isOpen
onClose={props.closeOverlay}
variant={ModalVariant.small}
data-test="channel-modal"
aria-labelledby="cluster-channel-modal-title"
>
<ClusterChannelModal {...props} close={props.closeOverlay} cancel={props.closeOverlay} />
</ModalWrapper>
</Modal>
);
};

Expand Down
Loading