@openshift-assisted/ui-lib 2.12.3-cim → 2.12.5-cim

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/build/@types/cim/components/ClusterDeployment/ClusterDeploymentDetailsForm.d.ts +2 -1
  2. package/build/@types/cim/components/ClusterDeployment/ClusterDeploymentDetailsForm.d.ts.map +1 -1
  3. package/build/@types/cim/components/ClusterDeployment/ClusterDetailsFormFields.d.ts +1 -0
  4. package/build/@types/cim/components/ClusterDeployment/ClusterDetailsFormFields.d.ts.map +1 -1
  5. package/build/@types/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsForm.d.ts.map +1 -1
  6. package/build/@types/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsStep.d.ts.map +1 -1
  7. package/build/@types/cim/components/Hypershift/HostedClusterWizard/DetailsStep/types.d.ts +6 -1
  8. package/build/@types/cim/components/Hypershift/HostedClusterWizard/DetailsStep/types.d.ts.map +1 -1
  9. package/build/@types/cim/components/Hypershift/hooks/useSupportedOCPVersions.d.ts +1 -1
  10. package/build/@types/cim/components/Hypershift/hooks/useSupportedOCPVersions.d.ts.map +1 -1
  11. package/build/@types/cim/components/helpers/versions.d.ts +3 -3
  12. package/build/@types/cim/components/helpers/versions.d.ts.map +1 -1
  13. package/build/@types/cim/types/k8s/agent-service-config.d.ts +6 -0
  14. package/build/@types/cim/types/k8s/agent-service-config.d.ts.map +1 -1
  15. package/build/@types/common/components/clusterConfiguration/index.d.ts +0 -1
  16. package/build/@types/common/components/clusterConfiguration/index.d.ts.map +1 -1
  17. package/build/@types/common/components/clusterWizard/clusterDetailsValidation.d.ts.map +1 -1
  18. package/build/@types/common/components/clusterWizard/types.d.ts +6 -0
  19. package/build/@types/common/components/clusterWizard/types.d.ts.map +1 -1
  20. package/build/@types/common/components/ui/OpenShiftSelectWithSearch.d.ts +11 -0
  21. package/build/@types/common/components/ui/OpenShiftSelectWithSearch.d.ts.map +1 -0
  22. package/build/@types/common/components/ui/OpenShiftVersionDropdown.d.ts +8 -8
  23. package/build/@types/common/components/ui/OpenShiftVersionDropdown.d.ts.map +1 -1
  24. package/build/@types/common/components/ui/OpenShiftVersionModal.d.ts +11 -0
  25. package/build/@types/common/components/ui/OpenShiftVersionModal.d.ts.map +1 -0
  26. package/build/@types/ocm/components/clusterConfiguration/OcmOpenShiftVersionSelect.d.ts.map +1 -1
  27. package/build/cjs/cim/components/ClusterDeployment/ClusterDeploymentDetailsForm.js +4 -3
  28. package/build/cjs/cim/components/ClusterDeployment/ClusterDeploymentDetailsForm.js.map +1 -1
  29. package/build/cjs/cim/components/ClusterDeployment/ClusterDetailsFormFields.js +37 -11
  30. package/build/cjs/cim/components/ClusterDeployment/ClusterDetailsFormFields.js.map +1 -1
  31. package/build/cjs/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsForm.js +23 -3
  32. package/build/cjs/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsForm.js.map +1 -1
  33. package/build/cjs/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsStep.js +3 -1
  34. package/build/cjs/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsStep.js.map +1 -1
  35. package/build/cjs/cim/components/Hypershift/hooks/useSupportedOCPVersions.js +3 -3
  36. package/build/cjs/cim/components/Hypershift/hooks/useSupportedOCPVersions.js.map +1 -1
  37. package/build/cjs/cim/components/helpers/versions.js +10 -5
  38. package/build/cjs/cim/components/helpers/versions.js.map +1 -1
  39. package/build/cjs/common/components/clusterConfiguration/index.js +1 -3
  40. package/build/cjs/common/components/clusterConfiguration/index.js.map +1 -1
  41. package/build/cjs/common/components/clusterWizard/clusterDetailsValidation.js +1 -0
  42. package/build/cjs/common/components/clusterWizard/clusterDetailsValidation.js.map +1 -1
  43. package/build/cjs/common/components/ui/OpenShiftSelectWithSearch.js +55 -0
  44. package/build/cjs/common/components/ui/OpenShiftSelectWithSearch.js.map +1 -0
  45. package/build/cjs/common/components/ui/OpenShiftVersionDropdown.css +7 -0
  46. package/build/cjs/common/components/ui/OpenShiftVersionDropdown.js +50 -27
  47. package/build/cjs/common/components/ui/OpenShiftVersionDropdown.js.map +1 -1
  48. package/build/cjs/common/components/ui/OpenShiftVersionModal.css +7 -0
  49. package/build/cjs/common/components/ui/OpenShiftVersionModal.js +28 -0
  50. package/build/cjs/common/components/ui/OpenShiftVersionModal.js.map +1 -0
  51. package/build/cjs/ocm/components/clusterConfiguration/OcmOpenShiftVersionSelect.js +5 -1
  52. package/build/cjs/ocm/components/clusterConfiguration/OcmOpenShiftVersionSelect.js.map +1 -1
  53. package/lib/cim/components/ClusterDeployment/ClusterDeploymentDetailsForm.tsx +13 -5
  54. package/lib/cim/components/ClusterDeployment/ClusterDetailsFormFields.tsx +57 -13
  55. package/lib/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsForm.tsx +45 -3
  56. package/lib/cim/components/Hypershift/HostedClusterWizard/DetailsStep/DetailsStep.tsx +4 -0
  57. package/lib/cim/components/Hypershift/HostedClusterWizard/DetailsStep/types.ts +6 -1
  58. package/lib/cim/components/Hypershift/hooks/useSupportedOCPVersions.ts +3 -2
  59. package/lib/cim/components/helpers/versions.ts +19 -4
  60. package/lib/cim/types/k8s/agent-service-config.ts +7 -0
  61. package/lib/common/components/clusterConfiguration/index.ts +0 -1
  62. package/lib/common/components/clusterWizard/clusterDetailsValidation.ts +1 -0
  63. package/lib/common/components/clusterWizard/types.ts +7 -0
  64. package/lib/common/components/ui/OpenShiftSelectWithSearch.tsx +130 -0
  65. package/lib/common/components/ui/OpenShiftVersionDropdown.css +7 -0
  66. package/lib/common/components/ui/OpenShiftVersionDropdown.tsx +101 -56
  67. package/lib/common/components/ui/OpenShiftVersionModal.css +7 -0
  68. package/lib/common/components/ui/OpenShiftVersionModal.tsx +61 -0
  69. package/lib/ocm/components/clusterConfiguration/OcmOpenShiftVersionSelect.tsx +11 -1
  70. package/package.json +3 -3
  71. package/build/@types/common/components/clusterConfiguration/OpenShiftVersionSelect.d.ts +0 -10
  72. package/build/@types/common/components/clusterConfiguration/OpenShiftVersionSelect.d.ts.map +0 -1
  73. package/build/cjs/common/components/clusterConfiguration/OpenShiftVersionSelect.js +0 -55
  74. package/build/cjs/common/components/clusterConfiguration/OpenShiftVersionSelect.js.map +0 -1
  75. package/lib/common/components/clusterConfiguration/OpenShiftVersionSelect.tsx +0 -81
@@ -1,17 +1,22 @@
1
1
  import { Form } from '@patternfly/react-core';
2
2
  import { useFormikContext } from 'formik';
3
3
  import * as React from 'react';
4
- import { InputField, OpenShiftVersionSelect, PullSecret } from '../../../../../common';
4
+ import { InputField, PullSecret } from '../../../../../common';
5
5
  import { useTranslation } from '../../../../../common/hooks/use-translation-wrapper';
6
6
  import { BaseDnsHelperText } from '../../../ClusterDeployment/ClusterDetailsFormFields';
7
7
  import { useTemptiflySync } from '../../hooks/useTemptiflySync';
8
8
  import { DetailsFormProps, DetailsFormValues } from './types';
9
+ import { OpenShiftVersionDropdown } from '../../../../../common/components/ui/OpenShiftVersionDropdown';
10
+ import { OpenShiftVersionModal } from '../../../../../common/components/ui/OpenShiftVersionModal';
9
11
 
10
12
  const DetailsForm: React.FC<DetailsFormProps> = ({
11
13
  onValuesChanged,
12
14
  extensionAfter,
13
15
  ocpVersions,
16
+ allVersions,
14
17
  }) => {
18
+ const { t } = useTranslation();
19
+ const [openshiftVersionModalOpen, setOpenshiftVersionModalOpen] = React.useState(false);
15
20
  const { values } = useFormikContext<DetailsFormValues>();
16
21
  useTemptiflySync({ values, onValuesChanged });
17
22
 
@@ -20,7 +25,30 @@ const DetailsForm: React.FC<DetailsFormProps> = ({
20
25
  nameInputRef.current?.focus();
21
26
  }, []);
22
27
 
23
- const { t } = useTranslation();
28
+ const selectOptions = React.useMemo(
29
+ () =>
30
+ ocpVersions.map((version) => ({
31
+ label: version.label,
32
+ value: version.value,
33
+ })),
34
+ [ocpVersions],
35
+ );
36
+
37
+ const additionalSelectOptions = React.useMemo(() => {
38
+ if (
39
+ values.customOpenshiftSelect &&
40
+ !selectOptions.some((option) => option.value === values.customOpenshiftSelect?.value)
41
+ ) {
42
+ return [
43
+ {
44
+ value: values.customOpenshiftSelect.value,
45
+ label: values.customOpenshiftSelect.label,
46
+ },
47
+ ];
48
+ }
49
+ return [];
50
+ }, [selectOptions, values.customOpenshiftSelect]);
51
+
24
52
  return (
25
53
  <Form>
26
54
  <InputField
@@ -38,7 +66,21 @@ const DetailsForm: React.FC<DetailsFormProps> = ({
38
66
  placeholder="example.com"
39
67
  isRequired
40
68
  />
41
- <OpenShiftVersionSelect versions={ocpVersions} />
69
+ <OpenShiftVersionDropdown
70
+ name="openshiftVersion"
71
+ items={selectOptions}
72
+ versions={ocpVersions}
73
+ showReleasesLink={false}
74
+ showOpenshiftVersionModal={() => setOpenshiftVersionModalOpen(true)}
75
+ customItems={additionalSelectOptions}
76
+ />
77
+ {openshiftVersionModalOpen && (
78
+ <OpenShiftVersionModal
79
+ allVersions={allVersions}
80
+ setOpenshiftVersionModalOpen={setOpenshiftVersionModalOpen}
81
+ />
82
+ )}
83
+
42
84
  <PullSecret />
43
85
  </Form>
44
86
  );
@@ -26,6 +26,7 @@ const useDetailsFormik: UseDetailsFormik = ({
26
26
  openshiftVersion: getDefaultOpenShiftVersion(ocpVersions),
27
27
  pullSecret: initPullSecret,
28
28
  baseDnsDomain: initBaseDomain,
29
+ customOpenshiftSelect: null,
29
30
  }),
30
31
  [], // eslint-disable-line react-hooks/exhaustive-deps
31
32
  );
@@ -53,6 +54,8 @@ const DetailsStep: React.FC<DetailsStepProps> = ({
53
54
  supportedVersionsCM,
54
55
  }) => {
55
56
  const ocpVersions = useSupportedOCPVersions(clusterImages, supportedVersionsCM);
57
+ const allVersions = useSupportedOCPVersions(clusterImages, supportedVersionsCM, true);
58
+
56
59
  const [initialValues, validationSchema] = useDetailsFormik({
57
60
  ocpVersions,
58
61
  usedClusterNames,
@@ -70,6 +73,7 @@ const DetailsStep: React.FC<DetailsStepProps> = ({
70
73
  <DetailsForm
71
74
  onValuesChanged={onValuesChanged}
72
75
  ocpVersions={ocpVersions}
76
+ allVersions={allVersions}
73
77
  extensionAfter={extensionAfter}
74
78
  />
75
79
  </Formik>
@@ -8,6 +8,7 @@ export type DetailsFormValues = {
8
8
  openshiftVersion: string;
9
9
  pullSecret: string;
10
10
  baseDnsDomain: string;
11
+ customOpenshiftSelect: OpenshiftVersionOptionType | null;
11
12
  };
12
13
 
13
14
  type UseDetailsFormikArgs = {
@@ -19,7 +20,10 @@ type UseDetailsFormikArgs = {
19
20
 
20
21
  export type UseDetailsFormik = (
21
22
  args: UseDetailsFormikArgs,
22
- ) => [DetailsFormValues, ObjectSchema<Omit<DetailsFormValues, 'openshiftVersion'>>];
23
+ ) => [
24
+ DetailsFormValues,
25
+ ObjectSchema<Omit<DetailsFormValues, 'openshiftVersion' | 'customOpenshiftSelect'>>,
26
+ ];
23
27
 
24
28
  export type DetailsStepProps = {
25
29
  usedClusterNames: string[];
@@ -35,4 +39,5 @@ export type DetailsStepProps = {
35
39
 
36
40
  export type DetailsFormProps = Pick<DetailsStepProps, 'onValuesChanged' | 'extensionAfter'> & {
37
41
  ocpVersions: OpenshiftVersionOptionType[];
42
+ allVersions: OpenshiftVersionOptionType[];
38
43
  };
@@ -11,9 +11,10 @@ export const getSupportedCM = (configMaps: ConfigMapK8sResource[]) =>
11
11
  export const useSupportedOCPVersions = (
12
12
  clusterImages: ClusterImageSetK8sResource[],
13
13
  supportedVersionsCM?: ConfigMapK8sResource,
14
+ extended?: boolean,
14
15
  ) => {
15
16
  return React.useMemo(() => {
16
- const ocpVersions = getOCPVersions(clusterImages, false);
17
+ const ocpVersions = getOCPVersions(clusterImages, false, undefined, extended);
17
18
  const supportedVersionsString = supportedVersionsCM?.data?.['supported-versions'];
18
19
  if (supportedVersionsString) {
19
20
  try {
@@ -25,5 +26,5 @@ export const useSupportedOCPVersions = (
25
26
  }
26
27
  }
27
28
  return ocpVersions;
28
- }, [clusterImages, supportedVersionsCM]);
29
+ }, [clusterImages, extended, supportedVersionsCM?.data]);
29
30
  };
@@ -4,6 +4,7 @@ import {
4
4
  AgentClusterInstallK8sResource,
5
5
  ClusterImageSetK8sResource,
6
6
  ClusterVersionK8sResource,
7
+ OsImage,
7
8
  } from '../../types';
8
9
  import { OpenshiftVersionOptionType } from '../../../common';
9
10
  import { OpenshiftVersion } from '@openshift-assisted/types/assisted-installer-service';
@@ -36,8 +37,12 @@ const getSupportLevelFromChannel = (
36
37
 
37
38
  export const supportedNutanixPlatforms = ['x86_64', 'x86-64'];
38
39
 
39
- export const isValidImageSet = (cis: ClusterImageSetK8sResource, architectures?: string[]) => {
40
- if (cis.metadata?.labels?.visible !== 'true') {
40
+ export const isValidImageSet = (
41
+ cis: ClusterImageSetK8sResource,
42
+ architectures?: string[],
43
+ extended?: boolean,
44
+ ) => {
45
+ if (!extended && cis.metadata?.labels?.visible !== 'true') {
41
46
  return false;
42
47
  }
43
48
  if (!architectures) {
@@ -51,10 +56,15 @@ export const isValidImageSet = (cis: ClusterImageSetK8sResource, architectures?:
51
56
  export const getOCPVersions = (
52
57
  clusterImageSets: ClusterImageSetK8sResource[],
53
58
  isNutanix?: boolean | undefined,
59
+ osImages?: OsImage[],
60
+ extended?: boolean,
54
61
  ): OpenshiftVersionOptionType[] => {
62
+ const ocpImageVersions = Array.from(
63
+ new Set(osImages?.map((osImage) => osImage.openshiftVersion)),
64
+ );
55
65
  const versions = clusterImageSets
56
66
  .filter((clusterImageSet) =>
57
- isValidImageSet(clusterImageSet, isNutanix ? supportedNutanixPlatforms : undefined),
67
+ isValidImageSet(clusterImageSet, isNutanix ? supportedNutanixPlatforms : undefined, extended),
58
68
  )
59
69
  .map((clusterImageSet): OpenshiftVersionOptionType => {
60
70
  const version = getVersionFromReleaseImage(clusterImageSet.spec?.releaseImage);
@@ -67,6 +77,10 @@ export const getOCPVersions = (
67
77
  supportLevel: 'production', // getSupportLevelFromChannel(clusterImageSet.metadata?.labels?.channel),
68
78
  };
69
79
  })
80
+ .filter((ocpVersion) => {
81
+ const ver = ocpVersion.version.split('.').slice(0, 2).join('.');
82
+ return !!ocpImageVersions.length ? ocpImageVersions.includes(ver) : true;
83
+ })
70
84
  .sort(
71
85
  (versionA, versionB) => /* descending */ -1 * versionA.label.localeCompare(versionB.label),
72
86
  );
@@ -86,8 +100,9 @@ export const getSelectedVersion = (
86
100
  const selectedClusterImage = clusterImages.find(
87
101
  (ci) => ci.metadata?.name === agentClusterInstall?.spec?.imageSetRef?.name,
88
102
  );
103
+
89
104
  return selectedClusterImage
90
- ? getOCPVersions([selectedClusterImage])?.[0]?.version
105
+ ? getOCPVersions([selectedClusterImage], undefined, undefined, true)?.[0]?.version
91
106
  : agentClusterInstall?.spec?.imageSetRef?.name;
92
107
  };
93
108
 
@@ -11,6 +11,13 @@ type StorageConfig = {
11
11
  };
12
12
  };
13
13
 
14
+ export type OsImage = {
15
+ cpuArchitecture: string;
16
+ openshiftVersion: string;
17
+ url: string;
18
+ version: string;
19
+ };
20
+
14
21
  export type AgentServiceConfigConditionType = 'DeploymentsHealthy' | 'ReconcileCompleted';
15
22
 
16
23
  export type AgentServiceConfigK8sResource = K8sResourceCommon & {
@@ -1,6 +1,5 @@
1
1
  export { default as SNOControlGroup } from './SNOControlGroup';
2
2
  export { default as SNODisclaimer } from './SNODisclaimer';
3
- export { default as OpenShiftVersionSelect } from './OpenShiftVersionSelect';
4
3
  export { default as UploadSSH } from './UploadSSH';
5
4
  export { default as ProxyFields, ProxyInputFields } from './ProxyFields';
6
5
  export { default as DownloadIsoModal } from './DownloadIsoModal';
@@ -76,6 +76,7 @@ export const getClusterDetailsInitialValues = ({
76
76
  diskEncryption: cluster?.diskEncryption ?? {},
77
77
  cpuArchitecture: cluster?.cpuArchitecture || getDefaultCpuArchitecture(),
78
78
  platform: cluster?.platform?.type || 'none',
79
+ customOpenshiftSelect: null,
79
80
  userManagedNetworking: cluster?.userManagedNetworking || false,
80
81
  };
81
82
  };
@@ -15,6 +15,7 @@ import {
15
15
  } from '../../types/hosts';
16
16
  import { TangServer } from '../clusterConfiguration/DiskEncryptionFields/DiskEncryptionValues';
17
17
  import { WizardStepsValidationMap } from './validationsInfoUtils';
18
+ import { OpenshiftVersionOptionType } from '../../types';
18
19
 
19
20
  export type ClusterDetailsValues = {
20
21
  name: string;
@@ -31,6 +32,7 @@ export type ClusterDetailsValues = {
31
32
  diskEncryption: DiskEncryption;
32
33
  cpuArchitecture: string;
33
34
  platform: PlatformType;
35
+ customOpenshiftSelect: OpenshiftVersionOptionType | null;
34
36
  userManagedNetworking: boolean;
35
37
  };
36
38
 
@@ -67,3 +69,8 @@ export type ValidationActionLinkProps<S extends string> = {
67
69
  };
68
70
 
69
71
  export type ClusterOperatorProps = Pick<Cluster, 'openshiftVersion'> & { clusterId: Cluster['id'] };
72
+
73
+ export type ItemDropdown = {
74
+ label: string;
75
+ value: string;
76
+ }[];
@@ -0,0 +1,130 @@
1
+ import React, { Dispatch, SetStateAction } from 'react';
2
+ import {
3
+ FormHelperText,
4
+ Select,
5
+ SelectOption,
6
+ SelectOptionObject,
7
+ SelectVariant,
8
+ } from '@patternfly/react-core';
9
+ import { OpenshiftVersionOptionType } from '../../types';
10
+ import { HelperTextType } from './OpenShiftVersionDropdown';
11
+ import { useTranslation } from '../../hooks/use-translation-wrapper';
12
+
13
+ type OpenshiftSelectWithSearchProps = {
14
+ versions: OpenshiftVersionOptionType[];
15
+ getHelperText?: HelperTextType;
16
+ setCustomOpenshiftSelect: Dispatch<SetStateAction<OpenshiftVersionOptionType | undefined>>;
17
+ };
18
+
19
+ export const OpenShiftSelectWithSearch: React.FunctionComponent<OpenshiftSelectWithSearchProps> = ({
20
+ versions,
21
+ getHelperText,
22
+ setCustomOpenshiftSelect,
23
+ }: OpenshiftSelectWithSearchProps) => {
24
+ const [isOpen, setIsOpen] = React.useState(false);
25
+ const [selected, setSelected] = React.useState<string>('');
26
+ const { t } = useTranslation();
27
+
28
+ const selectOptions = React.useMemo(
29
+ () =>
30
+ versions.map((version) => ({
31
+ value: version.value,
32
+ label:
33
+ version.supportLevel === 'beta'
34
+ ? version.label + ' - ' + t('ai:Developer preview release')
35
+ : version.label,
36
+ version: version,
37
+ })),
38
+ [versions, t],
39
+ );
40
+
41
+ const onToggle = (isOpen: boolean) => {
42
+ setIsOpen(isOpen);
43
+ };
44
+
45
+ const onSelect = (
46
+ _event: React.MouseEvent | React.ChangeEvent,
47
+ selection: string | SelectOptionObject,
48
+ ) => {
49
+ const value = typeof selection === 'string' ? selection : selection.toString();
50
+ const selectedOption = selectOptions.find((option) => option.value === value);
51
+
52
+ if (selectedOption) {
53
+ setSelected(value);
54
+ setCustomOpenshiftSelect(selectedOption.version);
55
+ setIsOpen(false);
56
+ }
57
+ };
58
+
59
+ const onClear = () => {
60
+ setSelected('');
61
+ setCustomOpenshiftSelect(undefined);
62
+ setIsOpen(false);
63
+ };
64
+
65
+ const onFilter = (e: React.ChangeEvent<HTMLInputElement> | null, value: string) => {
66
+ if (!value || value === '') {
67
+ return selectOptions.map((option) => (
68
+ <SelectOption key={option.value} value={option.value}>
69
+ {option.label}
70
+ </SelectOption>
71
+ ));
72
+ }
73
+
74
+ const filteredOptions = selectOptions.filter((option) =>
75
+ option.label.toLowerCase().includes(value.toLowerCase()),
76
+ );
77
+
78
+ if (filteredOptions.length === 0) {
79
+ return [
80
+ <SelectOption key="no-results" isDisabled>
81
+ {t('ai:No results found for "{{value}}"', { value })}
82
+ </SelectOption>,
83
+ ];
84
+ }
85
+
86
+ return filteredOptions.map((option) => (
87
+ <SelectOption key={option.value} value={option.value}>
88
+ {option.label}
89
+ </SelectOption>
90
+ ));
91
+ };
92
+
93
+ const helperText = getHelperText && getHelperText(selected, true);
94
+
95
+ return (
96
+ <>
97
+ <Select
98
+ id="customOpenshiftSelect"
99
+ variant={SelectVariant.typeahead}
100
+ typeAheadAriaLabel={t('ai:Select OpenShift version')}
101
+ placeholderText={t('ai:Select an OpenShift version')}
102
+ isOpen={isOpen}
103
+ onToggle={onToggle}
104
+ onSelect={onSelect}
105
+ onClear={onClear}
106
+ selections={selected}
107
+ onFilter={onFilter}
108
+ className="pf-u-w-100"
109
+ maxHeight="300px"
110
+ isCreatable={false}
111
+ >
112
+ {selectOptions.map((option) => (
113
+ <SelectOption key={option.value} value={option.value}>
114
+ {option.label}
115
+ </SelectOption>
116
+ ))}
117
+ </Select>
118
+
119
+ {helperText && <FormHelperText>{helperText}</FormHelperText>}
120
+
121
+ {!helperText && (
122
+ <FormHelperText>
123
+ {t(
124
+ 'ai:Select an OpenShift version from the list or use the type ahead to narrow down the list.',
125
+ )}
126
+ </FormHelperText>
127
+ )}
128
+ </>
129
+ );
130
+ };
@@ -0,0 +1,7 @@
1
+ #form-input-openshiftVersion-field-dropdown .pf-c-dropdown__menu ul,
2
+ #form-input-openshiftVersion-field-dropdown .pf-c-dropdown__menu li {
3
+ margin: 0;
4
+ padding: 0;
5
+ list-style: none !important;
6
+ list-style-type: none !important;
7
+ }
@@ -1,114 +1,159 @@
1
1
  import React from 'react';
2
2
  import {
3
+ FormGroup,
4
+ FormHelperText,
5
+ Button,
3
6
  DropdownItem,
4
7
  DropdownToggle,
5
8
  Dropdown,
6
- HelperText,
7
- FormGroup,
8
- DropdownSeparator,
9
+ DropdownGroup,
10
+ Divider,
9
11
  } from '@patternfly/react-core';
10
12
  import { CaretDownIcon } from '@patternfly/react-icons/dist/js/icons/caret-down-icon';
11
13
 
12
14
  import { OpenshiftVersionOptionType } from '../../types';
13
- import { TFunction } from 'i18next';
14
15
  import { useTranslation } from '../../hooks/use-translation-wrapper';
15
- import { useField } from 'formik';
16
+ import { useField, useFormikContext } from 'formik';
16
17
  import { getFieldId } from './formik';
17
18
  import ExternalLink from './ExternalLink';
18
19
  import { OCP_RELEASES_PAGE } from '../../config';
20
+ import { ClusterDetailsValues, ItemDropdown } from '../clusterWizard';
21
+ import './OpenShiftVersionDropdown.css';
19
22
 
20
- export type HelperTextType = (
21
- versions: OpenshiftVersionOptionType[],
22
- value: string | undefined,
23
- t: TFunction,
24
- ) => JSX.Element | null;
23
+ export type HelperTextType = (value: string | undefined, inModal?: boolean) => JSX.Element | null;
25
24
 
26
25
  type OpenShiftVersionDropdownProps = {
27
26
  name: string;
28
- items: {
29
- label: string;
30
- value: string;
31
- }[];
27
+ items: ItemDropdown;
32
28
  versions: OpenshiftVersionOptionType[];
33
- getHelperText: HelperTextType;
29
+ getHelperText?: HelperTextType;
34
30
  showReleasesLink: boolean;
31
+ showOpenshiftVersionModal: () => void;
32
+ customItems: ItemDropdown;
35
33
  };
36
34
 
35
+ const getParsedVersions = (items: ItemDropdown) => {
36
+ const versionsY = Array.from(new Set(items.map((val) => val.value.match(/^\d+\.(\d+)/)?.[1])));
37
+
38
+ const parsedVersions = versionsY.map((y) => ({
39
+ y: y,
40
+ versions: items.filter((val) => val.value.match(/^\d+\.(\d+)/)?.[1] === y),
41
+ }));
42
+ return { parsedVersions: parsedVersions.reverse() };
43
+ };
37
44
  export const OpenShiftVersionDropdown = ({
38
45
  name,
39
46
  items,
40
47
  versions,
41
48
  getHelperText,
42
49
  showReleasesLink,
50
+ showOpenshiftVersionModal,
51
+ customItems,
43
52
  }: OpenShiftVersionDropdownProps) => {
44
- const [field, , { setValue }] = useField(name);
53
+ const [field, , { setValue }] = useField<string>(name);
45
54
  const [isOpen, setOpen] = React.useState(false);
46
55
  const { t } = useTranslation();
47
56
  const fieldId = getFieldId(name, 'input');
48
- const isDisabled = versions.length === 0;
57
+ const {
58
+ values: { customOpenshiftSelect },
59
+ } = useFormikContext<ClusterDetailsValues>();
60
+ const [current, setCurrent] = React.useState<string>();
49
61
 
50
- const { defaultLabel, defaultValue } = React.useMemo(() => {
51
- const defaultVersion = versions.find((item) => item.default);
52
- return {
53
- defaultLabel: defaultVersion?.label || '',
54
- defaultValue: defaultVersion?.value || '',
55
- };
56
- }, [versions]);
62
+ React.useEffect(() => {
63
+ const defaultVersion = customOpenshiftSelect
64
+ ? customOpenshiftSelect
65
+ : versions.find((item) => item.default);
66
+ setCurrent(defaultVersion?.label || '');
67
+ setValue(defaultVersion?.value || '');
68
+ // eslint-disable-next-line react-hooks/exhaustive-deps
69
+ }, [customOpenshiftSelect]);
57
70
 
58
- const [helperText, setHelperText] = React.useState(getHelperText(versions, defaultValue, t));
59
- const [current, setCurrent] = React.useState<string>(defaultLabel);
60
-
61
- const versionsY = Array.from(new Set(items.map((val) => val.value.match(/^\d+\.(\d+)/)?.[1])));
62
- const lastVersion = versionsY.slice(-1)[0];
63
-
64
- const parsedVersions = versionsY.map((y) => ({
65
- y: y,
66
- versions: items.filter((val) => val.value.match(/^\d+\.(\d+)/)?.[1] === y),
67
- }));
68
-
69
- const dropdownItems = parsedVersions.map(({ y, versions }) => {
71
+ const parsedVersionsForItems = getParsedVersions(items);
72
+ let lastY: string | undefined = '';
73
+ const dropdownItems = parsedVersionsForItems.parsedVersions.map(({ y, versions }) => {
70
74
  const items = versions.map(({ value, label }) => (
71
- <DropdownItem key={value} id={value}>
75
+ <DropdownItem key={value} id={value} value={value}>
72
76
  {label}
73
77
  </DropdownItem>
74
78
  ));
75
79
 
76
- if (y !== lastVersion) {
77
- items.push(<DropdownSeparator key={`separator-${y || ''}`} />);
80
+ if (lastY !== null && y !== lastY) {
81
+ items.push(<Divider key={`separator-${y || ''}`} />);
78
82
  }
79
-
83
+ lastY = y;
80
84
  return items;
81
85
  });
82
86
 
87
+ const parsedVersionsForCustomItems = getParsedVersions(customItems);
88
+ let lastCustomY: string | undefined = '';
89
+ const customDropdownItems = parsedVersionsForCustomItems.parsedVersions.map(({ y, versions }) => {
90
+ const customItems = versions.map(({ value, label }) => (
91
+ <DropdownItem key={value} id={value} value={value}>
92
+ {label}
93
+ </DropdownItem>
94
+ ));
95
+ if (lastCustomY !== null && y !== lastCustomY) {
96
+ customItems.push(<Divider key={`separator-${y || ''}`} />);
97
+ }
98
+ lastCustomY = y;
99
+ return customItems;
100
+ });
101
+
102
+ const dropdownGroup = [
103
+ dropdownItems.length && (
104
+ <DropdownGroup label="Latest releases" key="latest-releases">
105
+ {dropdownItems}
106
+ </DropdownGroup>
107
+ ),
108
+ customDropdownItems.length && (
109
+ <DropdownGroup label="Custom releases" key="custom-releases">
110
+ {customDropdownItems}
111
+ </DropdownGroup>
112
+ ),
113
+ <DropdownGroup key="all-available-versions">
114
+ <DropdownItem key="all-versions" id="all-versions" onSelect={(e) => e.preventDefault()}>
115
+ <Button
116
+ variant="link"
117
+ isInline
118
+ onClick={() => showOpenshiftVersionModal()}
119
+ id="show-all-versions"
120
+ >
121
+ {t('ai:Show all available versions')}
122
+ </Button>
123
+ </DropdownItem>
124
+ </DropdownGroup>,
125
+ ].filter(Boolean);
126
+
83
127
  const onSelect = React.useCallback(
84
128
  (event?: React.SyntheticEvent<HTMLDivElement>) => {
85
- const newLabel = event?.currentTarget.innerText;
129
+ const newLabel = event?.currentTarget.textContent;
86
130
  const newValue = event?.currentTarget.id;
87
- if (newLabel && newValue) {
131
+ if (newLabel && newValue && event.currentTarget.id !== 'all-versions') {
88
132
  setCurrent(newLabel);
89
133
  setValue(newValue);
90
- setHelperText(getHelperText(versions, newValue, t));
91
134
  setOpen(false);
92
135
  }
93
136
  },
94
- [setCurrent, setHelperText, setOpen, getHelperText, t, versions, setValue],
137
+ [setValue],
95
138
  );
96
139
 
97
- const toggle = React.useMemo(
140
+ const dropdownToggle = React.useMemo(
98
141
  () => (
99
142
  <DropdownToggle
100
- onToggle={(val) => setOpen(!isDisabled && val)}
143
+ id={fieldId}
144
+ className="pf-u-w-100"
145
+ onToggle={(val) => setOpen(val)}
101
146
  toggleIndicator={CaretDownIcon}
102
- isDisabled={isDisabled}
103
147
  isText
104
- className="pf-u-w-100"
105
148
  >
106
- {current}
149
+ {current || t('ai:OpenShift version')}
107
150
  </DropdownToggle>
108
151
  ),
109
- [setOpen, current, isDisabled],
152
+ [fieldId, current, t],
110
153
  );
111
154
 
155
+ const helperText = getHelperText && getHelperText(field.value);
156
+
112
157
  return (
113
158
  <FormGroup
114
159
  id={`form-control__${fieldId}`}
@@ -118,15 +163,15 @@ export const OpenShiftVersionDropdown = ({
118
163
  >
119
164
  <Dropdown
120
165
  {...field}
121
- name={name}
122
- id={fieldId}
123
- onSelect={onSelect}
124
- dropdownItems={dropdownItems}
125
- toggle={toggle}
166
+ id={`${fieldId}-dropdown`}
126
167
  isOpen={isOpen}
168
+ onSelect={onSelect}
169
+ toggle={dropdownToggle}
170
+ dropdownItems={dropdownGroup}
127
171
  className="pf-u-w-100"
128
172
  />
129
- <HelperText style={{ display: 'inherit' }}>{helperText}</HelperText>
173
+
174
+ {helperText && <FormHelperText>{helperText}</FormHelperText>}
130
175
  {showReleasesLink && (
131
176
  <ExternalLink href={`${window.location.origin}/${OCP_RELEASES_PAGE}`}>
132
177
  <span data-ouia-id="openshift-releases-link">
@@ -0,0 +1,7 @@
1
+ #available-openshift-versions-modal {
2
+ height: 44vh;
3
+ display: flex;
4
+ }
5
+ #customOpenshiftSelect .pf-c-select__menu {
6
+ max-height: 22vh;
7
+ }