Skip to content

Commit

Permalink
feat: [M3-8676] - Provide Customer Notice for OS Distro Nearing EOL/E…
Browse files Browse the repository at this point in the history
…OS (#11253)

* feat: [M3-8676] - Notice for OS Distro Nearing EOL/EOS

* Added changeset: Notice for OS Distro Nearing EOL/EOS

* operator change

* formatted text and udpated state management

* replaced new states with existing useImageQuery()

* added decprecated notice for all ImageSelect references
  • Loading branch information
harsh-akamai authored Dec 11, 2024
1 parent 0bd360b commit 4afffcb
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 75 deletions.
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-11253-added-1731495888267.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Added
---

Notice for OS Distro Nearing EOL/EOS ([#11253](https://github.com/linode/manager/pull/11253))
164 changes: 106 additions & 58 deletions packages/manager/src/components/ImageSelect/ImageSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { Autocomplete } from '@linode/ui';
import { Autocomplete, Box, Notice, Stack, Typography } from '@linode/ui';
import { DateTime } from 'luxon';
import React, { useMemo } from 'react';

import { imageFactory } from 'src/factories/images';
import { useAllImagesQuery } from 'src/queries/images';
import { formatDate } from 'src/utilities/formatDate';

import { OSIcon } from '../OSIcon';
import { ImageOption } from './ImageOption';
import {
getAPIFilterForImageSelect,
getDisabledImages,
getFilteredImagesForImageSelect,
isImageDeprecated,
} from './utilities';

import type { Image, RegionSite } from '@linode/api-v4';
Expand Down Expand Up @@ -131,69 +134,114 @@ export const ImageSelect = (props: Props) => {
return options.find((option) => option.id === selected) ?? null;
}, [multiple, options, selected]);

const selectedDeprecatedImages = useMemo(() => {
if (!value) {
return false;
}
if (Array.isArray(value)) {
return value.filter((img) => isImageDeprecated(img));
}
return isImageDeprecated(value) && [value];
}, [value]);

if (options.length === 1 && onChange && selectIfOnlyOneOption && !multiple) {
onChange(options[0]);
}

return (
<Autocomplete
groupBy={(option) => {
if (option.id === 'any/all') {
return '';
<Box>
<Autocomplete
groupBy={(option) => {
if (option.id === 'any/all') {
return '';
}
if (!option.is_public) {
return 'My Images';
}

return option.vendor ?? '';
}}
renderOption={(props, option, state) => {
const { key, ...rest } = props;

return (
<ImageOption
disabledOptions={disabledImages[option.id]}
item={option}
key={key}
props={rest}
selected={state.selected}
/>
);
}}
textFieldProps={{
InputProps: {
startAdornment:
!multiple && value && !Array.isArray(value) ? (
<OSIcon
fontSize="24px"
height="24px"
os={value.vendor ?? ''}
pl={1}
pr={2}
/>
) : null,
},
}}
clearOnBlur
disableSelectAll
label={label || 'Images'}
loading={isLoading}
options={sortedOptions}
placeholder={placeholder || 'Choose an image'}
{...rest}
disableClearable={
rest.disableClearable ??
(selectIfOnlyOneOption && options.length === 1 && !multiple)
}
if (!option.is_public) {
return 'My Images';
onChange={(_, value) =>
multiple && Array.isArray(value)
? onChange(value)
: !multiple && !Array.isArray(value) && onChange(value)
}

return option.vendor ?? '';
}}
renderOption={(props, option, state) => {
const { key, ...rest } = props;

return (
<ImageOption
disabledOptions={disabledImages[option.id]}
item={option}
key={key}
props={rest}
selected={state.selected}
/>
);
}}
textFieldProps={{
InputProps: {
startAdornment:
!multiple && value && !Array.isArray(value) ? (
<OSIcon
fontSize="24px"
height="24px"
os={value.vendor ?? ''}
pl={1}
pr={2}
/>
) : null,
},
}}
clearOnBlur
disableSelectAll
label={label || 'Images'}
loading={isLoading}
options={sortedOptions}
placeholder={placeholder || 'Choose an image'}
{...rest}
disableClearable={
rest.disableClearable ??
(selectIfOnlyOneOption && options.length === 1 && !multiple)
}
onChange={(_, value) =>
multiple && Array.isArray(value)
? onChange(value)
: !multiple && !Array.isArray(value) && onChange(value)
}
errorText={rest.errorText ?? error?.[0].reason}
getOptionDisabled={(option) => Boolean(disabledImages[option.id])}
multiple={multiple}
value={value}
/>
errorText={rest.errorText ?? error?.[0].reason}
getOptionDisabled={(option) => Boolean(disabledImages[option.id])}
multiple={multiple}
value={value}
/>

<Stack>
{selectedDeprecatedImages &&
selectedDeprecatedImages.map((image) => (
<Notice
dataTestId="os-distro-deprecated-image-notice"
key={image.id}
spacingBottom={0}
spacingTop={16}
variant="warning"
>
{image.eol && DateTime.fromISO(image.eol) > DateTime.now() ? (
<Typography fontFamily={(theme) => theme.font.bold}>
{image.label} will reach its end-of-life on{' '}
{formatDate(image.eol ?? '', { format: 'MM/dd/yyyy' })}. After
this date, this OS distribution will no longer receive
security updates or technical support. We recommend selecting
a newer supported version to ensure continued security and
stability for your linodes.
</Typography>
) : (
<Typography fontFamily={(theme) => theme.font.bold}>
{image.label} reached its end-of-life on{' '}
{formatDate(image.eol ?? '', { format: 'MM/dd/yyyy' })}. This
OS distribution will no longer receive security updates or
technical support. We recommend selecting a newer supported
version to ensure continued security and stability for your
linodes.
</Typography>
)}
</Notice>
))}
</Stack>
</Box>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,6 @@ export const StackScriptForm = React.memo((props: Props) => {
rows={1}
value={description.value}
/>
<ImageSelect
textFieldProps={{
required: true,
tooltipText:
'Select which images are compatible with this StackScript. "Any/All" allows you to use private images.',
}}
anyAllOption
data-qa-stackscript-target-select
disabled={disabled}
errorText={hasErrorFor('images')}
label="Target Images"
multiple
onChange={onSelectChange}
placeholder="Select image(s)"
value={selectedImages}
variant="public"
/>
</StyledGridWithTips>
<StyledGridWithTips>
<StyledNotice>
Expand All @@ -126,6 +109,23 @@ export const StackScriptForm = React.memo((props: Props) => {
</StyledNotice>
</StyledGridWithTips>
</Grid>
<ImageSelect
textFieldProps={{
required: true,
tooltipText:
'Select which images are compatible with this StackScript. "Any/All" allows you to use private images.',
}}
anyAllOption
data-qa-stackscript-target-select
disabled={disabled}
errorText={hasErrorFor('images')}
label="Target Images"
multiple
onChange={onSelectChange}
placeholder="Select image(s)"
value={selectedImages}
variant="public"
/>
<TextField
InputProps={{ sx: { maxWidth: '100%' } }}
data-qa-stackscript-script
Expand Down

0 comments on commit 4afffcb

Please sign in to comment.