Skip to content

Commit

Permalink
Merge pull request kubevirt-ui#2059 from pcbailey/bug-fix-project-sel…
Browse files Browse the repository at this point in the history
…ect-in-add-volume-modal

CNV-43176: Allow user to select namespace in Add volume modal
  • Loading branch information
openshift-merge-bot[bot] authored Jul 17, 2024
2 parents 2ec57bf + 8353c3f commit c5f9c60
Show file tree
Hide file tree
Showing 16 changed files with 143 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import { DEFAULT_PREFERENCE_LABEL } from '@catalog/CreateFromInstanceTypes/utils
import { IoK8sApiCoreV1PersistentVolumeClaim } from '@kubevirt-ui/kubevirt-api/kubernetes';
import HelpTextIcon from '@kubevirt-utils/components/HelpTextIcon/HelpTextIcon';
import TabModal from '@kubevirt-utils/components/TabModal/TabModal';
import { DEFAULT_NAMESPACE } from '@kubevirt-utils/constants/constants';
import { ALL_NAMESPACES_SESSION_KEY } from '@kubevirt-utils/hooks/constants';
import { useCDIUpload } from '@kubevirt-utils/hooks/useCDIUpload/useCDIUpload';
import { UPLOAD_STATUS } from '@kubevirt-utils/hooks/useCDIUpload/utils';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import useStorageProfileClaimPropertySets from '@kubevirt-utils/hooks/useStorageProfileClaimPropertySets';
import { BootableVolume } from '@kubevirt-utils/resources/bootableresources/types';
import { kubevirtConsole } from '@kubevirt-utils/utils/utils';
import { getValidNamespace, kubevirtConsole } from '@kubevirt-utils/utils/utils';
import { useActiveNamespace } from '@openshift-console/dynamic-plugin-sdk';
import { Form, PopoverPosition, Title } from '@patternfly/react-core';

Expand All @@ -34,7 +32,6 @@ import { createBootableVolume } from './utils/utils';
import './AddBootableVolumeModal.scss';

type AddBootableVolumeModalProps = {
enforceNamespace?: string;
isOpen: boolean;
onClose: () => void;
onCreateVolume?: (
Expand All @@ -45,23 +42,23 @@ type AddBootableVolumeModalProps = {
};

const AddBootableVolumeModal: FC<AddBootableVolumeModalProps> = ({
enforceNamespace,
isOpen,
onClose,
onCreateVolume,
}) => {
const { t } = useKubevirtTranslation();
const [activeNamespace] = useActiveNamespace();
const selectedNamespace =
activeNamespace === ALL_NAMESPACES_SESSION_KEY ? undefined : activeNamespace;
const namespace = enforceNamespace ?? selectedNamespace ?? DEFAULT_NAMESPACE;
const namespace = getValidNamespace(activeNamespace);

const [bootableVolume, setBootableVolume] = useState<AddBootableVolumeState>({
...initialBootableVolumeState,
bootableVolumeNamespace: namespace,
});

const [bootableVolume, setBootableVolume] = useState<AddBootableVolumeState>(
initialBootableVolumeState,
);
const [sourceType, setSourceType] = useState<DROPDOWN_FORM_SELECTION>(
DROPDOWN_FORM_SELECTION.UPLOAD_IMAGE,
);

const applyStorageProfileState = useState<boolean>(true);

const { upload, uploadData } = useCDIUpload();
Expand Down Expand Up @@ -103,7 +100,6 @@ const AddBootableVolumeModal: FC<AddBootableVolumeModalProps> = ({
applyStorageProfileSettings: applyStorageProfileState[0],
bootableVolume,
claimPropertySets: claimPropertySetsData?.claimPropertySets,
namespace,
onCreateVolume,
sourceType,
uploadData,
Expand Down Expand Up @@ -137,7 +133,6 @@ const AddBootableVolumeModal: FC<AddBootableVolumeModalProps> = ({
applyStorageProfileState={applyStorageProfileState}
bootableVolume={bootableVolume}
claimPropertySetsData={claimPropertySetsData}
namespace={namespace}
setBootableVolumeField={setBootableVolumeField}
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { Dispatch, FC, SetStateAction, useState } from 'react';

import ApplyStorageProfileSettingsCheckbox from '@kubevirt-utils/components/ApplyStorageProfileSettingsCheckbox/ApplyStorageProfileSettingsCheckbox';
import CapacityInput from '@kubevirt-utils/components/CapacityInput/CapacityInput';
import ProjectDropdown from '@kubevirt-utils/components/ProjectDropdown/ProjectDropdown';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { UseStorageProfileClaimPropertySetsValue } from '@kubevirt-utils/hooks/useStorageProfileClaimPropertySets';
import { FormGroup, Grid, GridItem, TextInput } from '@patternfly/react-core';
Expand All @@ -15,21 +16,20 @@ type VolumeDestinationProps = {
applyStorageProfileState: [boolean, Dispatch<SetStateAction<boolean>>];
bootableVolume: AddBootableVolumeState;
claimPropertySetsData: UseStorageProfileClaimPropertySetsValue;
namespace: string;
setBootableVolumeField: SetBootableVolumeFieldType;
};

const VolumeDestination: FC<VolumeDestinationProps> = ({
applyStorageProfileState,
bootableVolume,
claimPropertySetsData,
namespace,
setBootableVolumeField,
}) => {
const { t } = useKubevirtTranslation();
const [showSCAlert, setShowSCAlert] = useState(false);

const { bootableVolumeName, size, storageClassName } = bootableVolume || {};
const { bootableVolumeName, bootableVolumeNamespace, size, storageClassName } =
bootableVolume || {};

const [applyStorageProfile, setApplyStorageProfile] = applyStorageProfileState;

Expand Down Expand Up @@ -76,7 +76,11 @@ const VolumeDestination: FC<VolumeDestinationProps> = ({
/>
</FormGroup>
<FormGroup label={t('Destination project')}>
<TextInput id="destination-project" isDisabled type="text" value={namespace} />
<ProjectDropdown
includeAllProjects={false}
onChange={setBootableVolumeField('bootableVolumeNamespace')}
selectedProject={bootableVolumeNamespace}
/>
</FormGroup>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const optionsValueLabelMapper = {
export type AddBootableVolumeState = {
annotations: { [key: string]: string };
bootableVolumeName: string;
bootableVolumeNamespace: string;
cronExpression: string;
labels: { [key: string]: string };
pvcName: string;
Expand All @@ -51,6 +52,7 @@ export type SetBootableVolumeFieldType = (
export const initialBootableVolumeState: AddBootableVolumeState = {
annotations: {},
bootableVolumeName: null,
bootableVolumeNamespace: null,
cronExpression: null,
labels: {},
pvcName: null,
Expand Down
13 changes: 8 additions & 5 deletions src/utils/components/AddBootableVolumeModal/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ type createBootableVolumeType = (input: {
applyStorageProfileSettings: boolean;
bootableVolume: AddBootableVolumeState;
claimPropertySets: ClaimPropertySets;
namespace: string;
onCreateVolume: (createdVolume: BootableVolume) => void;
sourceType: DROPDOWN_FORM_SELECTION;
uploadData: ({ dataVolume, file }: UploadDataProps) => Promise<void>;
Expand All @@ -39,19 +38,23 @@ export const createBootableVolume: createBootableVolumeType =
applyStorageProfileSettings,
bootableVolume,
claimPropertySets,
namespace,
onCreateVolume,
sourceType,
uploadData,
}) =>
async (dataSource: V1beta1DataSource) => {
const draftDataSource = setDataSourceMetadata(bootableVolume, namespace, dataSource);
const { bootableVolumeNamespace } = bootableVolume;
const draftDataSource = setDataSourceMetadata(
bootableVolume,
bootableVolumeNamespace,
dataSource,
);

const actionBySourceType: Record<string, () => Promise<V1beta1DataSource>> = {
[DROPDOWN_FORM_SELECTION.UPLOAD_IMAGE]: () =>
createBootableVolumeFromUpload(
bootableVolume,
namespace,
bootableVolumeNamespace,
applyStorageProfileSettings,
claimPropertySets,
draftDataSource,
Expand All @@ -60,7 +63,7 @@ export const createBootableVolume: createBootableVolumeType =
[DROPDOWN_FORM_SELECTION.USE_EXISTING_PVC]: () =>
createPVCBootableVolume(
bootableVolume,
namespace,
bootableVolumeNamespace,
applyStorageProfileSettings,
claimPropertySets,
draftDataSource,
Expand Down
5 changes: 5 additions & 0 deletions src/utils/components/ProjectDropdown/ProjectDropdown.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.project-dropdown {
width: 220px;
padding: 0 var(--pf-global--spacer--sm);
margin-bottom: var(--pf-global--spacer--md);
}
42 changes: 42 additions & 0 deletions src/utils/components/ProjectDropdown/ProjectDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { FC } from 'react';

import { modelToGroupVersionKind, ProjectModel } from '@kubevirt-ui/kubevirt-api/console';
import InlineFilterSelect from '@kubevirt-utils/components/FilterSelect/InlineFilterSelect';
import { getProjectOptions } from '@kubevirt-utils/components/ProjectDropdown/utils/utils';
import { ALL_PROJECTS } from '@kubevirt-utils/hooks/constants';
import { K8sResourceCommon, useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk';

type ProjectDropdownProps = {
includeAllProjects?: boolean;
onChange: (project: string) => void;
selectedProject: string;
};

const ProjectDropdown: FC<ProjectDropdownProps> = ({
includeAllProjects = true,
onChange,
selectedProject,
}) => {
const [projects] = useK8sWatchResource<K8sResourceCommon[]>({
groupVersionKind: modelToGroupVersionKind(ProjectModel),
isList: true,
namespaced: false,
});

const onSelect = (value: string) => {
onChange(value === ALL_PROJECTS ? '' : value);
};

return (
<div className="project-dropdown">
<InlineFilterSelect
options={getProjectOptions(includeAllProjects, projects)}
selected={selectedProject || ALL_PROJECTS}
setSelected={onSelect}
toggleProps={{ isFullWidth: true }}
/>
</div>
);
};

export default ProjectDropdown;
29 changes: 29 additions & 0 deletions src/utils/components/ProjectDropdown/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { modelToGroupVersionKind, ProjectModel } from '@kubevirt-ui/kubevirt-api/console';
import { ALL_PROJECTS } from '@kubevirt-utils/hooks/constants';
import { getName } from '@kubevirt-utils/resources/shared';
import { K8sResourceCommon } from '@openshift-console/dynamic-plugin-sdk';

export const getProjectOptions = (includeAllProjects: boolean, projects: K8sResourceCommon[]) => {
const projectOptions = projects
.sort((a, b) => getName(a).localeCompare(getName(b)))
.map((proj) => {
const name = getName(proj);
return {
children: name,
groupVersionKind: modelToGroupVersionKind(ProjectModel),
value: name,
};
});

const allProjects = includeAllProjects
? [
{
children: ALL_PROJECTS,
groupVersionKind: modelToGroupVersionKind(ProjectModel),
value: ALL_PROJECTS,
},
].concat(projectOptions)
: projectOptions;

return allProjects;
};
1 change: 1 addition & 0 deletions src/utils/hooks/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const ALL_NAMESPACES_SESSION_KEY = '#ALL_NS#';
export const ALL_NAMESPACES = 'all-namespaces';
export const ALL_PROJECTS = 'All projects';

export const EVENT_LOCALSTORAGE = 'event_localstorage';
16 changes: 13 additions & 3 deletions src/utils/resources/bootableresources/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import {
modelToGroupVersionKind,
PersistentVolumeClaimModel,
} from '@kubevirt-ui/kubevirt-api/console';
import { DataSourceModelGroupVersionKind } from '@kubevirt-ui/kubevirt-api/console/models/DataSourceModel';
import DataSourceModel, {
DataSourceModelGroupVersionKind,
} from '@kubevirt-ui/kubevirt-api/console/models/DataSourceModel';
import DataVolumeModel from '@kubevirt-ui/kubevirt-api/console/models/DataVolumeModel';
import {
V1beta1DataSource,
V1beta1DataVolume,
} from '@kubevirt-ui/kubevirt-api/containerized-data-importer/models';
import { IoK8sApiCoreV1PersistentVolumeClaim } from '@kubevirt-ui/kubevirt-api/kubernetes';
import { isEmpty } from '@kubevirt-utils/utils/utils';
import { isEmpty, kubevirtConsole } from '@kubevirt-utils/utils/utils';
import { k8sDelete } from '@openshift-console/dynamic-plugin-sdk';

import { BootableVolume } from './types';
Expand Down Expand Up @@ -44,8 +46,9 @@ export const getInstanceTypePrefix = (instanceTypeNamePrefix: string): string =>
return instanceTypeNamePrefix;
};

export const deleteDVAndPVC = async (
export const deleteDVAndRelatedResources = async (
dataVolume: BootableVolume | V1beta1DataVolume,
dataSource: BootableVolume | V1beta1DataSource,
persistentVolumeClaim: BootableVolume | IoK8sApiCoreV1PersistentVolumeClaim,
): Promise<void> => {
// We try to delete the created DV, if already GC, we want to fallback to delete the PVC
Expand All @@ -54,4 +57,11 @@ export const deleteDVAndPVC = async (
} catch {
await k8sDelete({ model: PersistentVolumeClaimModel, resource: persistentVolumeClaim });
}

// A PVC not found error will be thrown if the DV and DS are in the same try block
try {
await k8sDelete({ model: DataSourceModel, resource: dataSource });
} catch (error) {
kubevirtConsole.log(error);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { V1beta1DataSource } from '@kubevirt-ui/kubevirt-api/containerized-data-importer/models';
import { IoK8sApiCoreV1PersistentVolumeClaim } from '@kubevirt-ui/kubevirt-api/kubernetes';
import { VolumeSnapshotKind } from '@kubevirt-utils/components/SelectSnapshot/types';
import { ALL_PROJECTS } from '@kubevirt-utils/hooks/constants';
import { BootableVolume } from '@kubevirt-utils/resources/bootableresources/types';
import {
convertResourceArrayToMap,
Expand All @@ -22,12 +23,14 @@ import { Operator, useK8sWatchResource } from '@openshift-console/dynamic-plugin
type UseBootableVolumes = (namespace?: string) => UseBootableVolumesValues;

const useBootableVolumes: UseBootableVolumes = (namespace) => {
const projectsNamespace = namespace === ALL_PROJECTS ? null : namespace;

const [dataSources, loadedDataSources, dataSourcesError] = useK8sWatchResource<
V1beta1DataSource[]
>({
groupVersionKind: DataSourceModelGroupVersionKind,
isList: true,
namespace,
namespace: projectsNamespace,
selector: {
matchExpressions: [{ key: DEFAULT_PREFERENCE_LABEL, operator: Operator.Exists }],
},
Expand All @@ -39,14 +42,14 @@ const useBootableVolumes: UseBootableVolumes = (namespace) => {
>({
groupVersionKind: modelToGroupVersionKind(PersistentVolumeClaimModel),
isList: true,
namespace,
namespace: projectsNamespace,
});

// getting volumesnapshot as this can also be a source of DS
const [volumeSnapshots] = useK8sWatchResource<VolumeSnapshotKind[]>({
groupVersionKind: modelToGroupVersionKind(VolumeSnapshotModel),
isList: true,
namespace,
namespace: projectsNamespace,
});

const error = useMemo(() => dataSourcesError || loadErrorPVCs, [dataSourcesError, loadErrorPVCs]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { V1beta1VirtualMachineClusterPreference } from '@kubevirt-ui/kubevirt-ap
import DeleteModal from '@kubevirt-utils/components/DeleteModal/DeleteModal';
import { useModal } from '@kubevirt-utils/components/ModalProvider/ModalProvider';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { deleteDVAndPVC } from '@kubevirt-utils/resources/bootableresources/helpers';
import { deleteDVAndRelatedResources } from '@kubevirt-utils/resources/bootableresources/helpers';
import { asAccessReview } from '@kubevirt-utils/resources/shared';
import { Action, K8sVerb, useAccessReview } from '@openshift-console/dynamic-plugin-sdk';

Expand Down Expand Up @@ -63,7 +63,7 @@ const useBootableVolumesActions: BootableVolumesActionsProps = (source, preferen
createModal(({ isOpen, onClose }) => (
<DeleteModal
onDeleteSubmit={async () => {
await deleteDVAndPVC(source, source);
await deleteDVAndRelatedResources(source, source, source);
}}
headerText={t('Delete {{kind}}', { kind: source?.kind })}
isOpen={isOpen}
Expand Down
Loading

0 comments on commit c5f9c60

Please sign in to comment.