Skip to content

Commit 452ff62

Browse files
authored
Fixes and improvements for selecting WP version (#1189)
1 parent fdeb587 commit 452ff62

23 files changed

Lines changed: 398 additions & 334 deletions

‎src/components/add-site.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export default function AddSite( { className }: AddSiteProps ) {
7171
);
7272

7373
const { data: versions = [] } = useGetWordPressVersions();
74-
const latestStableVersion = versions.find( ( version ) => version.isLatest );
74+
const latestStableVersion = versions.find( ( version ) => version.value === 'latest' );
7575

7676
const initializeForm = useCallback( async () => {
7777
const siteName = await generateSiteName( sites );

‎src/components/onboarding.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export default function Onboarding() {
9090
} );
9191

9292
const { data: versions = [] } = useGetWordPressVersions();
93-
const latestStableVersion = versions.find( ( version ) => version.isLatest );
93+
const latestStableVersion = versions.find( ( version ) => version.value === 'latest' );
9494

9595
useEffect( () => {
9696
if ( latestStableVersion ) {

‎src/components/site-form.tsx‎

Lines changed: 9 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,14 @@ import { useI18n } from '@wordpress/react-i18n';
66
import { FormEvent, useRef, useState } from 'react';
77
import Button from 'src/components/button';
88
import FolderIcon from 'src/components/folder-icon';
9-
import offlineIcon from 'src/components/offline-icon';
109
import TextControlComponent from 'src/components/text-control';
11-
import { Tooltip } from 'src/components/tooltip';
10+
import { WPVersionSelector } from 'src/components/wp-version-selector';
1211
import { ACCEPTED_IMPORT_FILE_TYPES } from 'src/constants';
1312
import { useDocsLink } from 'src/hooks/use-docs-link';
14-
import { useOffline } from 'src/hooks/use-offline';
1513
import { isWindows } from 'src/lib/app-globals';
1614
import { cx } from 'src/lib/cx';
1715
import { generateCustomDomainFromSiteName } from 'src/lib/domains';
1816
import { getIpcApi } from 'src/lib/get-ipc-api';
19-
import { useGetWordPressVersions } from 'src/stores/wordpress-versions-api';
2017
import {
2118
DEFAULT_WORDPRESS_VERSION,
2219
ALLOWED_PHP_VERSIONS,
@@ -267,15 +264,10 @@ export const SiteForm = ( {
267264
}: SiteFormProps ) => {
268265
const { __, isRTL } = useI18n();
269266
const getDocsLink = useDocsLink();
270-
const isOffline = useOffline();
271267

272268
const shouldShowCustomDomainError = useCustomDomain && customDomainError;
273269
const errorCount = [ error, shouldShowCustomDomainError ].filter( Boolean ).length;
274270

275-
const offlineMessage = __( 'Changing WordPress version requires an internet connection.' );
276-
277-
const { data: wpVersions = [] } = useGetWordPressVersions();
278-
279271
const [ isAdvancedSettingsVisible, setAdvancedSettingsVisible ] = useState( false );
280272

281273
const handleAdvancedSettingsClick = () => {
@@ -412,40 +404,14 @@ export const SiteForm = ( {
412404
__nextHasNoMarginBottom
413405
/>
414406
</div>
415-
<div className="flex flex-col gap-1.5 leading-4">
416-
<label className="font-semibold" htmlFor="wp-version-select">
417-
{ __( 'WordPress version' ) }
418-
</label>
419-
<Tooltip
420-
disabled={ ! isOffline }
421-
icon={ offlineIcon }
422-
text={ offlineMessage }
423-
placement="top-start"
424-
className="flex flex-1 flex-col"
425-
>
426-
<SelectControl
427-
id="wp-version-select"
428-
value={ wpVersion }
429-
options={
430-
wpVersions.length > 0
431-
? wpVersions.map( ( { label, value } ) => ( {
432-
label,
433-
value,
434-
} ) )
435-
: [
436-
{
437-
label: DEFAULT_WORDPRESS_VERSION,
438-
value: DEFAULT_WORDPRESS_VERSION,
439-
},
440-
]
441-
}
442-
onChange={ setWpVersion }
443-
disabled={ isOffline }
444-
__next40pxDefaultSize
445-
__nextHasNoMarginBottom
446-
/>
447-
</Tooltip>
448-
</div>
407+
408+
<WPVersionSelector
409+
selectedValue={ wpVersion }
410+
onChange={ setWpVersion }
411+
fallbackOptions={ [
412+
{ label: __( 'Latest' ), value: DEFAULT_WORDPRESS_VERSION },
413+
] }
414+
/>
449415
</div>
450416

451417
{ setUseCustomDomain && setCustomDomain && (

‎src/components/tests/add-site.test.tsx‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,10 @@ jest.mock( 'src/stores/wordpress-versions-api', () => ( {
2525
value: '6.5.0-beta1',
2626
isBeta: true,
2727
isDevelopment: false,
28-
isLatest: false,
2928
label: '6.5.0-beta1',
3029
},
31-
{ value: '6.4.0', isBeta: false, isDevelopment: false, isLatest: true, label: '6.4' },
32-
{ value: '6.3.3', isBeta: false, isDevelopment: false, isLatest: false, label: '6.3.3' },
30+
{ value: '6.4.0', isBeta: false, isDevelopment: false, label: '6.4' },
31+
{ value: '6.3.3', isBeta: false, isDevelopment: false, label: '6.3.3' },
3332
],
3433
} ),
3534
selectWordPressVersionsWithLatest: jest.fn(),
@@ -144,7 +143,7 @@ describe( 'AddSite', () => {
144143
expect( mockCreateSite ).toHaveBeenCalledWith(
145144
'test',
146145
'My WordPress Website',
147-
expect.any( String ),
146+
'latest',
148147
undefined,
149148
false,
150149
expect.any( Function )

‎src/components/tests/onboarding.test.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ jest.mock( 'src/hooks/use-offline', () => ( {
3232
jest.mock( 'src/stores/wordpress-versions-api', () => ( {
3333
useGetWordPressVersions: jest.fn( () => ( {
3434
data: [
35-
{ isBeta: false, isDevelopment: false, isLatest: true, label: '6.3', value: '6.3.3' },
36-
{ isBeta: false, isDevelopment: false, isLatest: false, label: '6.2', value: '6.2.0' },
37-
{ isBeta: false, isDevelopment: false, isLatest: false, label: '6.1', value: '6.1.7' },
35+
{ isBeta: false, isDevelopment: false, label: '6.3', value: '6.3.3' },
36+
{ isBeta: false, isDevelopment: false, label: '6.2', value: '6.2.0' },
37+
{ isBeta: false, isDevelopment: false, label: '6.1', value: '6.1.7' },
3838
],
3939
isLoading: false,
4040
} ) ),

src/modules/site-settings/tests/wordpress-versions.test.ts renamed to src/components/wp-version-selector/add-wp-version-to-list.test.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
import { addWpVersionToList } from 'src/modules/site-settings/lib/wordpress-versions';
1+
import { addWpVersionToList } from './add-wp-version-to-list';
22

33
describe( 'addWpVersionToList', () => {
44
it( 'should add version to empty list', () => {
55
const options: Array< { label: string; value: string } > = [];
6-
addWpVersionToList( '6.4.2', options );
7-
expect( options ).toEqual( [ { label: '6.4.2', value: '6.4.2' } ] );
6+
const result = addWpVersionToList( { label: '6.4.2', value: '6.4.2' }, options );
7+
expect( result ).toEqual( [ { label: '6.4.2', value: '6.4.2' } ] );
88
} );
99

1010
it( 'should add version at the top when it is newer than all existing versions', () => {
1111
const options = [
1212
{ label: '6.4.1', value: '6.4.1' },
1313
{ label: '6.4.0', value: '6.4.0' },
1414
];
15-
addWpVersionToList( '6.4.3', options );
16-
expect( options ).toEqual( [
15+
const result = addWpVersionToList( { label: '6.4.3', value: '6.4.3' }, options );
16+
expect( result ).toEqual( [
1717
{ label: '6.4.3', value: '6.4.3' },
1818
{ label: '6.4.1', value: '6.4.1' },
1919
{ label: '6.4.0', value: '6.4.0' },
@@ -25,8 +25,8 @@ describe( 'addWpVersionToList', () => {
2525
{ label: '6.4.2', value: '6.4.2' },
2626
{ label: '6.4.1', value: '6.4.1' },
2727
];
28-
addWpVersionToList( '6.4.0', options );
29-
expect( options ).toEqual( [
28+
const result = addWpVersionToList( { label: '6.4.0', value: '6.4.0' }, options );
29+
expect( result ).toEqual( [
3030
{ label: '6.4.2', value: '6.4.2' },
3131
{ label: '6.4.1', value: '6.4.1' },
3232
{ label: '6.4.0', value: '6.4.0' },
@@ -38,8 +38,8 @@ describe( 'addWpVersionToList', () => {
3838
{ label: '6.4.2', value: '6.4.2' },
3939
{ label: '6.4.0', value: '6.4.0' },
4040
];
41-
addWpVersionToList( '6.4.1', options );
42-
expect( options ).toEqual( [
41+
const result = addWpVersionToList( { label: '6.4.1', value: '6.4.1' }, options );
42+
expect( result ).toEqual( [
4343
{ label: '6.4.2', value: '6.4.2' },
4444
{ label: '6.4.1', value: '6.4.1' },
4545
{ label: '6.4.0', value: '6.4.0' },
@@ -51,8 +51,8 @@ describe( 'addWpVersionToList', () => {
5151
{ label: '6.4.2', value: '6.4.2' },
5252
{ label: '6.4.0', value: '6.4.0' },
5353
];
54-
addWpVersionToList( 'nightly', options );
55-
expect( options ).toEqual( [
54+
const result = addWpVersionToList( { label: 'nightly', value: 'nightly' }, options );
55+
expect( result ).toEqual( [
5656
{ label: '6.4.2', value: '6.4.2' },
5757
{ label: '6.4.0', value: '6.4.0' },
5858
{ label: 'nightly', value: 'nightly' },
@@ -64,8 +64,8 @@ describe( 'addWpVersionToList', () => {
6464
{ label: 'nightly', value: 'nightly' },
6565
{ label: '6.4.0', value: '6.4.0' },
6666
];
67-
addWpVersionToList( '6.4.1', options );
68-
expect( options ).toEqual( [
67+
const result = addWpVersionToList( { label: '6.4.1', value: '6.4.1' }, options );
68+
expect( result ).toEqual( [
6969
{ label: 'nightly', value: 'nightly' },
7070
{ label: '6.4.1', value: '6.4.1' },
7171
{ label: '6.4.0', value: '6.4.0' },
@@ -77,8 +77,8 @@ describe( 'addWpVersionToList', () => {
7777
{ label: '6.4.2', value: '6.4.2' },
7878
{ label: '6.4.0', value: '6.4.0' },
7979
];
80-
addWpVersionToList( '6.4.1-beta2', options );
81-
expect( options ).toEqual( [
80+
const result = addWpVersionToList( { label: '6.4.1-beta2', value: '6.4.1-beta2' }, options );
81+
expect( result ).toEqual( [
8282
{ label: '6.4.2', value: '6.4.2' },
8383
{ label: '6.4.1-beta2', value: '6.4.1-beta2' },
8484
{ label: '6.4.0', value: '6.4.0' },
@@ -90,8 +90,8 @@ describe( 'addWpVersionToList', () => {
9090
{ label: '6.4.2', value: '6.4.2' },
9191
{ label: '6.4.0', value: '6.4.0' },
9292
];
93-
addWpVersionToList( '6.4.1-RC1', options );
94-
expect( options ).toEqual( [
93+
const result = addWpVersionToList( { label: '6.4.1-RC1', value: '6.4.1-RC1' }, options );
94+
expect( result ).toEqual( [
9595
{ label: '6.4.2', value: '6.4.2' },
9696
{ label: '6.4.1-RC1', value: '6.4.1-RC1' },
9797
{ label: '6.4.0', value: '6.4.0' },
@@ -104,8 +104,8 @@ describe( 'addWpVersionToList', () => {
104104
{ label: '6.4.1-beta2', value: '6.4.1-beta2' },
105105
{ label: '6.4.0', value: '6.4.0' },
106106
];
107-
addWpVersionToList( '6.4.1-beta1', options );
108-
expect( options ).toEqual( [
107+
const result = addWpVersionToList( { label: '6.4.1-beta1', value: '6.4.1-beta1' }, options );
108+
expect( result ).toEqual( [
109109
{ label: '6.4.2', value: '6.4.2' },
110110
{ label: '6.4.1-beta2', value: '6.4.1-beta2' },
111111
{ label: '6.4.1-beta1', value: '6.4.1-beta1' },
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import semver from 'semver';
2+
3+
type Option = { label: string; value: string };
4+
5+
export const addWpVersionToList = ( newOption: Option, options: Option[] ): Option[] => {
6+
const currentVer = semver.coerce( newOption.value );
7+
8+
// Being extra cautious here, if the version is not valid, we add it to the end of the list.
9+
if ( ! currentVer ) {
10+
return [ ...options, newOption ];
11+
}
12+
13+
const firstOlderVersionIndex = options.findIndex( ( compareVersion ) => {
14+
const compareVer = semver.coerce( compareVersion.value );
15+
return compareVer && semver.gt( currentVer, compareVer );
16+
} );
17+
18+
if ( firstOlderVersionIndex === -1 ) {
19+
return [ ...options, newOption ];
20+
} else {
21+
return [
22+
...options.slice( 0, firstOlderVersionIndex ),
23+
newOption,
24+
...options.slice( firstOlderVersionIndex ),
25+
];
26+
}
27+
};
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { SelectControl, Icon } from '@wordpress/components';
2+
import { warning } from '@wordpress/icons';
3+
import { useI18n } from '@wordpress/react-i18n';
4+
import offlineIcon from 'src/components/offline-icon';
5+
import { Tooltip } from 'src/components/tooltip';
6+
import { useOffline } from 'src/hooks/use-offline';
7+
import { cx } from 'src/lib/cx';
8+
import { isWordPressDevVersion } from 'src/lib/version-utils';
9+
import { isWordPressBetaVersion } from 'src/lib/wordpress-version-utils';
10+
import { useGetWordPressVersions } from 'src/stores/wordpress-versions-api';
11+
import { DEFAULT_WORDPRESS_VERSION } from 'vendor/wp-now/src/constants';
12+
import { addWpVersionToList } from './add-wp-version-to-list';
13+
14+
type WPVersionSelectorProps = {
15+
selectedValue: string;
16+
onChange: ( version: string ) => void;
17+
errorMessage?: string | null;
18+
disabled?: boolean;
19+
/** Is used if you want to add a custom option to the list, for example the current version of a site */
20+
extraOptions?: { label: string; value: string }[];
21+
/** Fallback options shown when available versions couldn't be fetched */
22+
fallbackOptions: { label: string; value: string }[];
23+
};
24+
25+
export const WPVersionSelector = ( {
26+
selectedValue,
27+
onChange,
28+
errorMessage,
29+
disabled = false,
30+
extraOptions,
31+
fallbackOptions,
32+
}: WPVersionSelectorProps ) => {
33+
const { __ } = useI18n();
34+
const isOffline = useOffline();
35+
const offlineMessage = __( 'Changing WordPress version requires an internet connection.' );
36+
const { data: wpVersions = [] } = useGetWordPressVersions();
37+
38+
let betaVersions: { label: string; value: string }[] = wpVersions.filter(
39+
( version ) => version.isBeta || version.isDevelopment
40+
);
41+
let stableVersions: { label: string; value: string }[] = wpVersions.filter(
42+
( version ) => ! version.isBeta && ! version.isDevelopment && version.value !== 'latest'
43+
);
44+
extraOptions?.forEach( ( extraOption ) => {
45+
const alreadyExists = wpVersions.some( ( version ) => version.value === extraOption.value );
46+
if ( alreadyExists ) {
47+
return;
48+
}
49+
50+
if (
51+
isWordPressBetaVersion( extraOption.value ) ||
52+
isWordPressDevVersion( extraOption.value )
53+
) {
54+
betaVersions = addWpVersionToList( extraOption, betaVersions );
55+
} else {
56+
stableVersions = addWpVersionToList( extraOption, stableVersions );
57+
}
58+
} );
59+
60+
return (
61+
<label className="flex flex-1 flex-col gap-1.5 leading-4">
62+
<span className="font-semibold flex items-center gap-2">
63+
{ __( 'WordPress version' ) }
64+
{ selectedValue !== 'latest' && (
65+
<Tooltip
66+
text={ __( 'WordPress Core automatic updates will be disabled for this site.' ) }
67+
placement="top"
68+
>
69+
<Icon icon={ warning } size={ 16 } />
70+
</Tooltip>
71+
) }
72+
</span>
73+
<Tooltip
74+
disabled={ ! isOffline }
75+
icon={ offlineIcon }
76+
text={ offlineMessage }
77+
placement="top-start"
78+
className="flex flex-1 flex-col"
79+
>
80+
<SelectControl
81+
className={ cx( errorMessage && 'error-select-control' ) }
82+
disabled={ disabled || isOffline }
83+
value={ selectedValue }
84+
onChange={ onChange }
85+
__next40pxDefaultSize
86+
__nextHasNoMarginBottom
87+
>
88+
{ wpVersions.length > 0 ? (
89+
<>
90+
<optgroup label={ __( 'Auto-updating' ) }>
91+
<option key={ DEFAULT_WORDPRESS_VERSION } value={ DEFAULT_WORDPRESS_VERSION }>
92+
{ __( 'latest' ) }
93+
</option>
94+
</optgroup>
95+
<optgroup label={ __( 'Beta & Nightly' ) }>
96+
{ betaVersions.map( ( { label, value } ) => (
97+
<option key={ value } value={ value }>
98+
{ label }
99+
</option>
100+
) ) }
101+
</optgroup>
102+
<optgroup label={ __( 'Stable Versions' ) }>
103+
{ stableVersions.map( ( { label, value } ) => (
104+
<option key={ value } value={ value }>
105+
{ label }
106+
</option>
107+
) ) }
108+
</optgroup>
109+
</>
110+
) : (
111+
fallbackOptions.map( ( { label, value } ) => (
112+
<option key={ value } value={ value }>
113+
{ label }
114+
</option>
115+
) )
116+
) }
117+
</SelectControl>
118+
</Tooltip>
119+
</label>
120+
);
121+
};

0 commit comments

Comments
 (0)