Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CNV-29444: Feature guide user to pipelines #1911

Merged
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
10 changes: 8 additions & 2 deletions locales/en/plugin__kubevirt-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@
"<0>Clicking \"Launch Remote Viewer\" will download a .vv file and launch <2>Remote Viewer</2></0><1><0>Remote Viewer</0> is available for most operating systems. To install it, search for it in GNOME Software or run the following:</1>": "<0>Clicking \"Launch Remote Viewer\" will download a .vv file and launch <2>Remote Viewer</2></0><1><0>Remote Viewer</0> is available for most operating systems. To install it, search for it in GNOME Software or run the following:</1>",
"<0>Deleting this PVC will also delete <2>{{pvcName}}</2> Data Volume</0>": "<0>Deleting this PVC will also delete <2>{{pvcName}}</2> Data Volume</0>",
"<0>Donuts chart represent current values.</0><1>Sparkline charts represent data over time</1>": "<0>Donuts chart represent current values.</0><1>Sparkline charts represent data over time</1>",
"<0>From the Volume table, select DataSources and PersistentVolumeClaims to boot your VirtualMachine.</0><1>To add a bootable volume that is not listed, click <2></2></1><2>Learn how to <2></2></2>": "<0>From the Volume table, select DataSources and PersistentVolumeClaims to boot your VirtualMachine.</0><1>To add a bootable volume that is not listed, click <2></2></1><2>Learn how to <2></2></2>",
"<0>List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.</0><1><0>{{kind}}</0><1>metadata</1><2>ownerReferences</2></1>": "<0>List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.</0><1><0>{{kind}}</0><1>metadata</1><2>ownerReferences</2></1>",
"<0>No volumes found</0>Click <2> Add volume</2> to add a volume to boot from": "<0>No volumes found</0>Click <2> Add volume</2> to add a volume to boot from",
"<0>No volumes found</0>To add a bootable volume, click <2></2> Add volume": "<0>No volumes found</0>To add a bootable volume, click <2></2> Add volume",
"<0>Store the key in a project secret.</0><1>The key will be stored after the machine is created</1>": "<0>Store the key in a project secret.</0><1>The key will be stored after the machine is created</1>",
"<0>The descheduler evicts a running pod so that the pod can be rescheduled on a more suitable node.</0><br/><2>Note: This setting is disabled if the VirtualMachine is not live migratable.</2>": "<0>The descheduler evicts a running pod so that the pod can be rescheduled on a more suitable node.</0><br/><2>Note: This setting is disabled if the VirtualMachine is not live migratable.</2>",
"<0>This is a Windows VirtualMachine but no Service for the RDP (Remote Desktop Protocol) can be found.</0><br/><2>For better experience accessing Windows console, it is recommended to use the RDP.<1>Create RDP Service</1></2>": "<0>This is a Windows VirtualMachine but no Service for the RDP (Remote Desktop Protocol) can be found.</0><br/><2>For better experience accessing Windows console, it is recommended to use the RDP.<1>Create RDP Service</1></2>",
Expand Down Expand Up @@ -99,6 +100,8 @@
"Add toleration to specify qualifying Nodes": "Add toleration to specify qualifying Nodes",
"Add tolerations to allow a VirtualMachine to schedule onto Nodes with matching taints.": "Add tolerations to allow a VirtualMachine to schedule onto Nodes with matching taints.",
"Add volume": "Add volume",
"Add Volume": "Add Volume",
"Add Volume.": "Add Volume.",
"Additional column list": "Additional column list",
"Additional columns": "Additional columns",
"Additional disks types and interfaces are available when the VirtualMachine is stopped.": "Additional disks types and interfaces are available when the VirtualMachine is stopped.",
Expand Down Expand Up @@ -314,6 +317,7 @@
"Create a new custom Template": "Create a new custom Template",
"Create a Virtual Machine from a template": "Create a Virtual Machine from a template",
"Create a virtual machine from a template (Quick start)": "Create a virtual machine from a template (Quick start)",
"Create a Windows boot source": "Create a Windows boot source",
"Create activation key": "Create activation key",
"Create DataSource": "Create DataSource",
"Create MigrationPolicy": "Create MigrationPolicy",
Expand Down Expand Up @@ -597,6 +601,9 @@
"Installed version": "Installed version",
"InstanceType": "InstanceType",
"InstanceTypes": "InstanceTypes",
"Interested in other <1>Bootable Volumes</1>? Click <4></4> to get started.": "Interested in other <1>Bootable Volumes</1>? Click <4></4> to get started.",
"Interested in using a <1>RHEL Bootable Volume</1>? Click <4></4> to get started.": "Interested in using a <1>RHEL Bootable Volume</1>? Click <4></4> to get started.",
"Interested in using a <1>Windows Bootable Volume</1>? Click <4></4> to get started.To learn more, follow the <7></7> quick start.": "Interested in using a <1>Windows Bootable Volume</1>? Click <4></4> to get started.To learn more, follow the <7></7> quick start.",
"Interface": "Interface",
"Interface Type": "Interface Type",
"Internal FQDN": "Internal FQDN",
Expand Down Expand Up @@ -1206,7 +1213,6 @@
"The VirtualMachine will have": "The VirtualMachine will have",
"The VirtualMachine you are creating does not have an available boot source. We recommended that you select a boot source to create the VirtualMachine.": "The VirtualMachine you are creating does not have an available boot source. We recommended that you select a boot source to create the VirtualMachine.",
"The VirtualMachineInstance has": "The VirtualMachineInstance has",
"The Volume table displays DataSources and PersistentVolumeClaims that VirtualMachines can boot from.<1>Click <1> Add volume</1> to boot from a volume that is not listed.</1>": "The Volume table displays DataSources and PersistentVolumeClaims that VirtualMachines can boot from.<1>Click <1> Add volume</1> to boot from a volume that is not listed.</1>",
"This field is required": "This field is required",
"This is a CD-ROM boot source": "This is a CD-ROM boot source",
"This key will override the SSH key secret set on the template": "This key will override the SSH key secret set on the template",
Expand Down
43 changes: 43 additions & 0 deletions src/utils/hooks/useQuickStarts/useQuickStarts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';

import {
getBestMatch,
groupQuickStartsByName,
} from '@kubevirt-utils/hooks/useQuickStarts/utils/utils';
import { QuickStartModel } from '@kubevirt-utils/models';
import {
getGroupVersionKindForModel,
useK8sWatchResource,
WatchK8sResult,
} from '@openshift-console/dynamic-plugin-sdk';
import { getDisabledQuickStarts, QuickStart } from '@patternfly/quickstarts';

const useQuickStarts = (filterDisabledQuickStarts = true): WatchK8sResult<QuickStart[]> => {
const preferredLanguage = useTranslation().i18n.language;
Copy link
Member

@metalice metalice May 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its nicer to deconstruct on left side then the right when using hooks

Copy link
Member Author

@pcbailey pcbailey May 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean. Could you explain a bit more and explain why?

Also, the code in this file is from console. I copied it over in my attempt to get quick starts working. If we end up having to use it I'll clean it up.


const [quickStarts, quickStartsLoaded, quickStartsError] = useK8sWatchResource<QuickStart[]>({
groupVersionKind: getGroupVersionKindForModel(QuickStartModel),
isList: true,
});

const bestMatchQuickStarts = useMemo(() => {
if (!quickStartsLoaded) {
return [];
}
const groupedQuickStarts = groupQuickStartsByName(quickStarts);

if (filterDisabledQuickStarts) {
const disabledQuickStarts = getDisabledQuickStarts();
disabledQuickStarts.forEach((quickStartName) => delete groupedQuickStarts[quickStartName]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of delete and modifing the obj u can filter and get a new obj. from running on one obj u modify a different obj. better to work on groupedQuickStarts and filter on it

}

return Object.values(groupedQuickStarts).map((quickStartsByName) =>
getBestMatch(quickStartsByName, preferredLanguage),
);
}, [quickStarts, quickStartsLoaded, filterDisabledQuickStarts, preferredLanguage]);

return [bestMatchQuickStarts, quickStartsLoaded, quickStartsError];
};

export default useQuickStarts;
3 changes: 3 additions & 0 deletions src/utils/hooks/useQuickStarts/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const LOCALIZATION_NAME_LABEL = 'console.openshift.io/name';
export const LOCALIZATION_LANGUAGE_LABEL = 'console.openshift.io/lang';
export const LOCALIZATION_COUNTRY_LABEL = 'console.openshift.io/country';
89 changes: 89 additions & 0 deletions src/utils/hooks/useQuickStarts/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {
LOCALIZATION_COUNTRY_LABEL,
LOCALIZATION_LANGUAGE_LABEL,
LOCALIZATION_NAME_LABEL,
} from '@kubevirt-utils/hooks/useQuickStarts/utils/constants';
import { QuickStart } from '@patternfly/quickstarts';

export const getQuickStartNameRef = (quickStart: QuickStart) =>
quickStart.metadata.labels?.[LOCALIZATION_NAME_LABEL] ||
quickStart.metadata.annotations?.[LOCALIZATION_NAME_LABEL] ||
quickStart.metadata.name;

export const groupQuickStartsByName = (quickStarts: QuickStart[]) => {
return quickStarts.reduce<Record<string, QuickStart[]>>((grouped, quickStart) => {
const name = getQuickStartNameRef(quickStart);
if (!grouped[name]) grouped[name] = [];
grouped[name].push(quickStart);
return grouped;
}, {});
};

/**
* Returns the QuickStart with the best localization match, for the given
* preferred language and preferred country. It prefers a match in this order:
*
* 1. QuickStart language and country are equal to the preferred language and country.
* This includes sample without language (fallbacks to en) and no country.
*
* 2. QuickStart language is equal to the preferred language.
* 1. And the quick starts has no country defined. (eg, select en quick starts is used for en-CA and en-GB)
* 2. Any country is defined. (eg, select en-CA quick starts is used for en-GB)
*
* 3. Fallback to an english quick starts
* (QuickStart language is en OR quick starts language is not defined):
* 1. Same country (use en-CA quick starts if preference is fr-CA)
* 2. No country
* 3. Any country (use en-CA quick starts if preference is en-US)
* @param quickStarts QuickStart custom resources
* @param language Language code
*/
export const getBestMatch = (quickStarts: QuickStart[], language: string): null | QuickStart => {
if (!quickStarts || !quickStarts.length) {
return null;
}
const preferredLanguage = (language || 'en').split('-')[0].toLowerCase();
const preferredCountry = ((language || '').split('-')[1] || '').toUpperCase();

let sameLanguageWithoutCountry: QuickStart = null;
let sameLanguageWithAnyCountry: QuickStart = null;
let fallbackLanguageSameCountry: QuickStart = null;
let fallbackLanguageNoCountry: QuickStart = null;
let fallbackLanguageAnyCountry: QuickStart = null;

for (const quickStart of quickStarts) {
const quickStartLanguage = (
quickStart.metadata?.labels?.[LOCALIZATION_LANGUAGE_LABEL] || 'en'
).toLowerCase();
const quickStartCountry = (
quickStart.metadata?.labels?.[LOCALIZATION_COUNTRY_LABEL] || ''
).toUpperCase();

if (quickStartLanguage === preferredLanguage && quickStartCountry === preferredCountry) {
return quickStart;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here u are returning the first match, and all other checks u are returning the last match?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is also from console. I'll clean it up as well if we end up using it.

}
if (quickStartLanguage === preferredLanguage) {
if (!quickStartCountry && !sameLanguageWithoutCountry) {
sameLanguageWithoutCountry = quickStart;
} else if (quickStartCountry && !sameLanguageWithAnyCountry) {
sameLanguageWithAnyCountry = quickStart;
}
}
if (quickStartLanguage === 'en') {
if (quickStartCountry === preferredCountry && !fallbackLanguageSameCountry) {
fallbackLanguageSameCountry = quickStart;
} else if (!quickStartCountry && !fallbackLanguageNoCountry) {
fallbackLanguageNoCountry = quickStart;
} else if (!fallbackLanguageAnyCountry) {
fallbackLanguageAnyCountry = quickStart;
}
}
}
Comment on lines +65 to +81
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consider changing the mechanism to a more readable one, this if, if else if isn't something we usually do.

return (
sameLanguageWithoutCountry ||
sameLanguageWithAnyCountry ||
fallbackLanguageSameCountry ||
fallbackLanguageNoCountry ||
fallbackLanguageAnyCountry
);
};
13 changes: 13 additions & 0 deletions src/utils/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,16 @@ export const UploadTokenRequestModel: K8sModel = {
namespaced: true,
plural: 'uploadtokenrequests',
};

export const QuickStartModel: K8sModel = {
abbr: 'CQS',
apiGroup: 'console.openshift.io',
apiVersion: 'v1',
crd: true,
kind: 'ConsoleQuickStart',
label: 'ConsoleQuickStart',
labelPlural: 'ConsoleQuickStarts',
namespaced: false,
plural: 'consolequickstarts',
propagationPolicy: 'Background',
};
2 changes: 2 additions & 0 deletions src/utils/resources/template/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const CUSTOM_TEMPLATES = 'custom-templates';

export const DATA_SOURCE_CRONJOB_LABEL = 'cdi.kubevirt.io/dataImportCron';

export const LINUX = 'linux';

export enum OS_NAME_TYPES {
centos = 'centos',
fedora = 'fedora',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
--pf-c-divider--after--BackgroundColor: var(--pf-global--palette--black-200);
}
&__HelpTextIcon {
max-width: 480px;
max-width: 575px;
}
&__page-loader {
height: 50vh;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
import React, { FC, useEffect, useState } from 'react';
import { Trans } from 'react-i18next';
import { getOSImagesNS } from 'src/views/clusteroverview/OverviewTab/inventory-card/utils/utils';

import SelectInstanceTypeSection from '@catalog/CreateFromInstanceTypes/components/SelectInstanceTypeSection/SelectInstanceTypeSection';
import VMDetailsSection from '@catalog/CreateFromInstanceTypes/components/VMDetailsSection/VMDetailsSection';
import HelpTextIcon from '@kubevirt-utils/components/HelpTextIcon/HelpTextIcon';
import Loading from '@kubevirt-utils/components/Loading/Loading';
import { DEFAULT_NAMESPACE } from '@kubevirt-utils/constants/constants';
import { ALL_NAMESPACES_SESSION_KEY } from '@kubevirt-utils/hooks/constants';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import useKubevirtUserSettings from '@kubevirt-utils/hooks/useKubevirtUserSettings/useKubevirtUserSettings';
import useBootableVolumes from '@kubevirt-utils/resources/bootableresources/hooks/useBootableVolumes';
import { useActiveNamespace } from '@openshift-console/dynamic-plugin-sdk';
import {
Bullseye,
Card,
Divider,
Grid,
GridItem,
List,
PopoverPosition,
} from '@patternfly/react-core';
import { Bullseye, Card, Divider, Grid, GridItem, List } from '@patternfly/react-core';

import AddBootableVolumeButton from './components/AddBootableVolumeButton/AddBootableVolumeButton';
import BootableVolumeList from './components/BootableVolumeList/BootableVolumeList';
import CreateFromInstanceTypeTitle from './components/CreateFromInstanceTypeTitle/CreateFromInstanceTypeTitle';
import CreateVMFooter from './components/CreateVMFooter/CreateVMFooter';
import SectionListItem from './components/SectionListItem/SectionListItem';
import useInstanceTypesAndPreferences from './state/hooks/useInstanceTypesAndPreferences';
Expand Down Expand Up @@ -80,24 +71,9 @@ const CreateFromInstanceType: FC = () => {
<AddBootableVolumeButton loadError={instanceTypesAndPreferencesData.loadError} />
}
headerText={
<>
{t('Select volume to boot from')}{' '}
<HelpTextIcon
bodyContent={
<>
<Trans ns="plugin__kubevirt-plugin" t={t}>
The Volume table displays DataSources and PersistentVolumeClaims that
VirtualMachines can boot from.
<div>
Click <b> Add volume</b> to boot from a volume that is not listed.
</div>
</Trans>
</>
}
className="create-vm-instance-type-section__HelpTextIcon"
position={PopoverPosition.right}
/>
</>
<CreateFromInstanceTypeTitle
instanceTypesAndPreferencesData={instanceTypesAndPreferencesData}
/>
}
sectionKey={INSTANCE_TYPES_SECTIONS.SELECT_VOLUME}
sectionState={sectionState}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.add-bootable-volume-link__inline-text {
padding-left: 0;
padding-right: 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, { FC } from 'react';

import { useInstanceTypeVMStore } from '@catalog/CreateFromInstanceTypes/state/useInstanceTypeVMStore';
import AddBootableVolumeModal from '@kubevirt-utils/components/AddBootableVolumeModal/AddBootableVolumeModal';
import { useModal } from '@kubevirt-utils/components/ModalProvider/ModalProvider';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import useCanCreateBootableVolume from '@kubevirt-utils/resources/bootableresources/hooks/useCanCreateBootableVolume';
import { Button, ButtonVariant } from '@patternfly/react-core';

import { getOSImagesNS } from '../../../../clusteroverview/OverviewTab/inventory-card/utils/utils';

import './AddBootableVolumeLink.scss';

type AddBootableVolumeLinkProps = {
hidePopover?: () => void;
loadError: Error;
text?: string;
};

const AddBootableVolumeLink: FC<AddBootableVolumeLinkProps> = ({
hidePopover,
loadError,
text,
}) => {
const { t } = useKubevirtTranslation();
const { createModal } = useModal();
const { onSelectCreatedVolume } = useInstanceTypeVMStore();

const sourceNamespace = getOSImagesNS();
const { canCreateDS, canCreatePVC } = useCanCreateBootableVolume(sourceNamespace);
const canCreate = canCreateDS || canCreatePVC;

return (
<Button
onClick={() => {
hidePopover?.();
createModal((props) => (
<AddBootableVolumeModal
enforceNamespace={sourceNamespace}
onCreateVolume={onSelectCreatedVolume}
{...props}
/>
));
}}
className="add-bootable-volume-link__inline-text"
isDisabled={!!loadError || !canCreate}
variant={ButtonVariant.link}
>
{text || t('Add Volume')}
</Button>
);
};

export default AddBootableVolumeLink;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.bootable-volume-empty-state {
&__icon-bar {
margin-top: var(--pf-v5-global--spacer--xl);
}

&__icon-bar :nth-child(2) {
margin-left: var(--pf-v5-global--spacer--2xl);
margin-right: var(--pf-v5-global--spacer--2xl);
}

&__icon {
height: 50px;
}

&__title {
margin-bottom: var(--pf-v5-global--spacer--md);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
import React, { FC } from 'react';
import { Trans } from 'react-i18next';

import AddBootableVolumeLink from '@catalog/CreateFromInstanceTypes/components/AddBootableVolumeLink/AddBootableVolumeLink';
import BootableVolumeOSIcons from '@catalog/CreateFromInstanceTypes/components/BootableVolumeEmptyState/BootableVolumeOSIcons';
import useInstanceTypesAndPreferences from '@catalog/CreateFromInstanceTypes/state/hooks/useInstanceTypesAndPreferences';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { EmptyState, Title } from '@patternfly/react-core';

import './BootableVolumeEmptyState.scss';

const BootableVolumeEmptyState: FC = () => {
const { t } = useKubevirtTranslation();
const { loadError } = useInstanceTypesAndPreferences();

return (
<EmptyState>
<Trans ns="plugin__kubevirt-plugin" t={t}>
<Title headingLevel="h3">No volumes found</Title>
Click <b> Add volume</b> to add a volume to boot from
</Trans>
<div className="bootable-volume-empty-state">
<Trans ns="plugin__kubevirt-plugin" t={t}>
<Title className="bootable-volume-empty-state__title" headingLevel="h3">
No volumes found
</Title>
To add a bootable volume, click <AddBootableVolumeLink loadError={loadError} /> Add volume
</Trans>
<BootableVolumeOSIcons />
</div>
</EmptyState>
);
};
Expand Down
Loading
Loading