Skip to content

Commit

Permalink
create: Show all operating systems
Browse files Browse the repository at this point in the history
This will show all operating systems, regardless of EOL or release
dates, but the recommended ones will be at the top.  Operating systems
with an expired EOL date will be annotated accordingly in the list.

The tests have been brought up-to-date with regard to what OSes are
actually in the list and are downloadable.

Fixes #509
  • Loading branch information
mvollmer committed Oct 25, 2024
1 parent cb84438 commit 7f32e2e
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 75 deletions.
82 changes: 42 additions & 40 deletions src/components/create-vm-dialog/createVmDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { useInit } from "hooks.js";
import cockpit from 'cockpit';
import store from "../../store.js";
import { MachinesConnectionSelector } from '../common/machinesConnectionSelector.jsx';
import { TypeaheadSelectWithHeaders } from '../common/typeaheadSelectWithHeaders.jsx';
import { FormHelper } from "cockpit-components-form-helper.jsx";
import { FileAutoComplete } from "cockpit-components-file-autocomplete.jsx";
import {
Expand Down Expand Up @@ -76,6 +77,7 @@ import {
correctSpecialCases,
filterReleaseEolDates,
getOSStringRepresentation,
getOSDescription,
needsRHToken,
isDownloadableOs,
loadOfflineToken,
Expand Down Expand Up @@ -405,10 +407,8 @@ const SourceRow = ({ connectionName, source, sourceType, networks, nodeDevices,
class OSRow extends React.Component {
constructor(props) {
super(props);
const IGNORE_VENDORS = ['ALTLinux', 'Mandriva', 'GNOME Project'];
const osInfoListExt = this.props.osInfoList
.map(os => correctSpecialCases(os))
.filter(os => filterReleaseEolDates(os) && !IGNORE_VENDORS.find(vendor => vendor == os.vendor))
.sort((a, b) => {
if (a.vendor == b.vendor) {
// Sort OS with numbered version by version
Expand All @@ -426,22 +426,33 @@ class OSRow extends React.Component {
return getOSStringRepresentation(a).toLowerCase() > getOSStringRepresentation(b).toLowerCase() ? 1 : -1;
});

const IGNORE_VENDORS = ['ALTLinux', 'Mandriva', 'GNOME Project'];
const newOsEntries = [];
const oldOsEntries = [];
for (const os of osInfoListExt) {
if (filterReleaseEolDates(os) && !IGNORE_VENDORS.find(vendor => vendor == os.vendor))
newOsEntries.push(os);
else
oldOsEntries.push(os);
}

const make_option = os => ({
value: os.shortId,
content: getOSStringRepresentation(os),
description: getOSDescription(os),
});

const selectOptions = [
{ header: _("Recommended operating systems") },
...newOsEntries.map(make_option),
{ header: _("Unsupported and older operating systems") },
...oldOsEntries.map(make_option),
];

this.state = {
typeAheadKey: Math.random(),
osEntries: osInfoListExt,
};
this.createValue = os => {
return ({
toString: function() { return this.displayName },
compareTo: function(value) {
if (typeof value == "string")
return this.shortId.toLowerCase().includes(value.toLowerCase()) || this.displayName.toLowerCase().includes(value.toLowerCase());
else
return this.shortId == value.shortId;
},
...os,
displayName: getOSStringRepresentation(os),
});
selectOptions,
osInfoListExt,
};
}

Expand All @@ -454,30 +465,21 @@ class OSRow extends React.Component {
data-loading={!!isLoading}
id="os-select-group"
label={_("Operating system")}>
<PFSelect
variant="typeahead"
key={this.state.typeAheadKey}
id='os-select'
isDisabled={isLoading}
selections={os ? this.createValue(os) : null}
typeAheadAriaLabel={_("Choose an operating system")}
placeholderText={_("Choose an operating system")}
onSelect={(event, value) => {
this.setState({
isOpen: false
});
onValueChanged('os', value);
}}
onClear={() => {
this.setState({ isOpen: false });
onValueChanged('os', null);
}}
onToggle={(_event, isOpen) => this.setState({ isOpen })}
isOpen={this.state.isOpen}
menuAppendTo="parent">
{this.state.osEntries.map(os => (<SelectOption key={os.id}
value={this.createValue(os)} />))}
</PFSelect>
<TypeaheadSelectWithHeaders key={this.state.typeAheadKey}
id='os-select'
isDisabled={isLoading}
placeholder={_("Choose an operating system")}
selectOptions={this.state.selectOptions}
selected={os?.shortId}
onSelect={(event, value) => {
const os = this.state.osInfoListExt.find(os => os.shortId === value);
if (os)
onValueChanged('os', os);
}}
onClearSelection={() => {
onValueChanged('os', null);
}}
/>
<FormHelper helperTextInvalid={validationStateOS == "error" && validationFailed.os} />
</FormGroup>
);
Expand Down
14 changes: 14 additions & 0 deletions src/components/create-vm-dialog/createVmDialogUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/

import cockpit from 'cockpit';
import React from 'react';
import { ExclamationTriangleIcon, OutlinedClockIcon } from "@patternfly/react-icons";

import {
getTodayYearShifted,
} from "../../helpers.js";

import * as python from "python.js";
import autoDetectOSScript from './autoDetectOS.py';

const _ = cockpit.gettext;

const ACCEPT_RELEASE_DATES_AFTER = getTodayYearShifted(-3);
const ACCEPT_EOL_DATES_AFTER = getTodayYearShifted(-1);
const RHSM_TOKEN = "rhsm-offline-token";
Expand Down Expand Up @@ -76,6 +82,14 @@ export function filterReleaseEolDates(os) {
);
}

export function getOSDescription(os) {
if (os.eolDate && compareDates(ACCEPT_EOL_DATES_AFTER, os.eolDate) < 0)
return <span><ExclamationTriangleIcon /> {cockpit.format(_("Vendor support ended $0"), os.eolDate)}</span>;
if (!os.eolDate && os.releaseDate && compareDates(ACCEPT_RELEASE_DATES_AFTER, os.releaseDate) < 0)
return <span><OutlinedClockIcon /> {cockpit.format(_("Released $0"), os.releaseDate)}</span>;
return null;
}

export function compareDates(a, b, emptyFirst = false) {
if (!a) {
if (!b) {
Expand Down
7 changes: 7 additions & 0 deletions src/machines.scss
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,10 @@
#storage-pool-delete-modal span.pf-v5-c-check__body {
margin-block-start: 0;
}

#os-select {
/* Don't get too tall */
max-block-size: min(20rem, 50vh);
/* Don't have a horizontal scrollbar */
overflow-y: auto;
}
71 changes: 36 additions & 35 deletions test/check-machines-create
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,16 @@ class TestMachinesCreate(machineslib.VirtualMachinesCase):
pixel_test_tag="auto"))

# check if older os are filtered
runner.checkFilteredOsTest(TestMachinesCreate.VmDialog(self, os_name=config.REDHAT_RHEL_4_7_FILTERED_OS,
pixel_test_tag="filter"))

runner.checkFilteredOsTest(TestMachinesCreate.VmDialog(self, os_name=config.MANDRIVA_2011_FILTERED_OS))
runner.checkFilteredOsTest(TestMachinesCreate.VmDialog(self, os_name=config.OLD_FILTERED_OS,
os_is_old=True))

runner.checkFilteredOsTest(TestMachinesCreate.VmDialog(self, os_name=config.MAGEIA_3_FILTERED_OS))
runner.checkFilteredOsTest(TestMachinesCreate.VmDialog(self, os_name=config.UNSUPPORTED_FILTERED_OS,
os_is_unsupported=True,
pixel_test_tag="filter-unsupported"))

# check that newer oses are present and searchable with substring match
runner.checkFilteredOsTest(TestMachinesCreate.VmDialog(self, os_name=config.WINDOWS_SERVER_10,
os_search_name=config.WINDOWS_SERVER_10_SHORT))
runner.checkFilteredOsTest(TestMachinesCreate.VmDialog(self, os_name=config.NOT_FOUND_FILTERED_OS,
os_is_not_found=True,
pixel_test_tag="filter"))

# check OS versions are sorted in alphabetical order
runner.checkSortedOsTest(TestMachinesCreate.VmDialog(self), [config.FEDORA_29, config.FEDORA_28])
Expand Down Expand Up @@ -935,7 +935,9 @@ vnc_password= "{vnc_passwd}"
TREE_URL = 'https://archive.fedoraproject.org/pub/archive/fedora/linux/releases/28/Server/x86_64/os'

# LINUX can be filtered if 3 years old
REDHAT_RHEL_4_7_FILTERED_OS = 'Red Hat Enterprise Linux 4.9'
OLD_FILTERED_OS = 'Red Hat Enterprise Linux 8.3 (Ootpa)'
UNSUPPORTED_FILTERED_OS = 'Fedora 34'
NOT_FOUND_FILTERED_OS = 'Red Hat Enterprise Linux 4.9'

FEDORA_28 = 'Fedora 28'
FEDORA_28_SHORTID = 'fedora28'
Expand All @@ -950,13 +952,6 @@ vnc_password= "{vnc_passwd}"

CENTOS_7 = 'CentOS 7'

MANDRIVA_2011_FILTERED_OS = 'Mandriva Linux 2011'

MAGEIA_3_FILTERED_OS = 'Mageia 3'

WINDOWS_SERVER_10 = 'Microsoft Windows 10'
WINDOWS_SERVER_10_SHORT = 'win'

class VmDialog:
vmId = 0

Expand All @@ -969,6 +964,9 @@ vnc_password= "{vnc_passwd}"
os_name="Fedora 28",
os_search_name=None,
os_short_id="fedora28",
os_is_unsupported=False,
os_is_old=False,
os_is_not_found=False,
expected_os_name=None,
is_unattended=None,
profile=None,
Expand Down Expand Up @@ -1017,6 +1015,9 @@ vnc_password= "{vnc_passwd}"
self.os_name = os_name
self.os_search_name = os_search_name
self.os_short_id = os_short_id
self.os_is_unsupported = os_is_unsupported
self.os_is_old = os_is_old
self.os_is_not_found = os_is_not_found
self.expected_os_name = expected_os_name
self.is_unattended = is_unattended
self.profile = profile
Expand Down Expand Up @@ -1138,15 +1139,13 @@ vnc_password= "{vnc_passwd}"
b.wait_not_present("#navbar-oops")

# re-input an OS which is "Fedora 28"
# need to click the extend button to show the OS list
b.click("#os-select-group button[aria-label=\"Options menu\"]")
b.set_input_text("#os-select-group input", "Fedora")
b.click("#os-select li button:contains('Fedora 28')")
b.wait_attr_contains("#os-select-group input", "value", "Fedora 28")
b.wait_not_present("#navbar-oops")

# click the 'X' button to clear the OS input and check there is no Ooops
b.click("#os-select-group button[aria-label=\"Clear all\"]")
b.click("#os-select-group button[aria-label=\"Clear input value\"]")
b.wait_attr("#os-select-group input", "value", "")
b.wait_not_present("#navbar-oops")

Expand Down Expand Up @@ -1176,23 +1175,25 @@ vnc_password= "{vnc_passwd}"

return self

def checkOsFiltered(self, present=False):
def checkOsFiltered(self):
b = self.browser

b.focus("#os-select-group input")
# os_search_name is meant to be used to test substring much
# os_search_name is meant to be used to test substring match
b.input_text(self.os_search_name or self.os_name)

if not present:
try:
with b.wait_timeout(5):
b.wait_in_text("#os-select li button", "No results found")
return self
except AssertionError:
# os found which is not ok
self.fail(f"{self.os_name} was not filtered")
if not self.os_is_not_found:
# There should be exactly one entry
b.wait_in_text("#os-select li button:not(:disabled)", self.os_name)
# It might have a description
if self.os_is_unsupported:
b.wait_in_text("#os-select li button:not(:disabled)", "Vendor support ended")
elif self.os_is_old:
b.wait_in_text("#os-select li button:not(:disabled)", "Released ")
else:
b.wait_visible(f"#os-select li button:contains({self.os_search_name})")
b.wait_in_text("#os-select li button", "No results found")

return self

def checkRhelIsDownloadable(self):
b = self.browser
Expand All @@ -1204,22 +1205,22 @@ vnc_password= "{vnc_passwd}"
b.wait_visible(f"#os-select li button:contains('{TestMachinesCreate.TestCreateConfig.RHEL_8_1}')")
b.wait_not_present(f"#os-select li button:contains('{TestMachinesCreate.TestCreateConfig.RHEL_7_1}')")
# make the select list go away to not obscure other elements
b.click("#os-select-group .pf-v5-c-select__toggle-button")
b.click("#os-select-group button.pf-v5-c-menu-toggle__button")
b.wait_not_present("#os-select li")

return self

def checkOsSorted(self, sorted_list):
b = self.browser

b.click("#os-select-group .pf-v5-c-select .pf-v5-c-button")
b.click("#os-select-group button.pf-v5-c-menu-toggle__button")

# Find the first OS from the sorted list, and get a text of it's next neighbour
next_os = b.text(f"#os-select-group li:contains({sorted_list[0]}) + li")
# The next neighbour should contain the second OS from the sorted list
self.assertEqual(next_os, sorted_list[1])
# make the select list go away to not obscure other elements
b.click("#os-select-group .pf-v5-c-select__toggle-button")
b.click("#os-select-group button.pf-v5-c-menu-toggle__button")
b.wait_not_present("#os-select li")
return self

Expand Down Expand Up @@ -1361,7 +1362,7 @@ vnc_password= "{vnc_passwd}"

b.wait_not_present("#offline-token")
if self.os_name:
b.click("#os-select-group > div button")
b.click("#os-select-group button.pf-v5-c-menu-toggle__button")
b.click(f"#os-select li:contains('{self.os_name}') button")
b.wait_attr("#os-select-group input", "value", self.os_name)

Expand Down Expand Up @@ -1992,7 +1993,7 @@ vnc_password= "{vnc_passwd}"
dialog.open() \

b.select_from_dropdown("#source-type", dialog.sourceType)
b.click("#os-select-group > div button")
b.click("#os-select-group button.pf-v5-c-menu-toggle__button")
b.click(f"#os-select li:contains('{dialog.os_name}') button")
b.wait_attr("#os-select-group input", "value", dialog.os_name)
if not dialog.offline_token_autofilled:
Expand Down

0 comments on commit 7f32e2e

Please sign in to comment.