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

[release-4.17] CNV-41403: localize loading #2225

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
20 changes: 5 additions & 15 deletions src/utils/components/EnvironmentEditor/EnvironmentForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { useImmer } from 'use-immer';

import { V1VirtualMachine } from '@kubevirt-ui/kubevirt-api/kubevirt';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { getNamespace } from '@kubevirt-utils/resources/shared';
import { isEmpty } from '@kubevirt-utils/utils/utils';
import { Button, Form } from '@patternfly/react-core';
import { PlusCircleIcon } from '@patternfly/react-icons';

Expand All @@ -11,7 +13,6 @@ import EnvironmentFormActions from './components/EnvironmentFormActions';
import EnvironmentFormSkeleton from './components/EnvironmentFormSkeleton';
import EnvironmentFormTitle from './components/EnvironmentFormTitle';
import useEnvironments from './hooks/useEnvironments';
import useEnvironmentsResources from './hooks/useEnvironmentsResources';

import './EnvironmentForm.scss';

Expand All @@ -25,15 +26,6 @@ const EnvironmentForm: FC<EnvironmentFormProps> = ({ onEditChange, updateVM, vm
const [temporaryVM, setTemporaryVM] = useImmer(vm);

const { t } = useKubevirtTranslation();
const ns = vm?.metadata?.namespace;

const {
configMaps,
error: loadError,
loaded,
secrets,
serviceAccounts,
} = useEnvironmentsResources(ns);

const {
edited,
Expand All @@ -55,7 +47,7 @@ const EnvironmentForm: FC<EnvironmentFormProps> = ({ onEditChange, updateVM, vm
[environments],
);

if (!loaded) return <EnvironmentFormSkeleton />;
if (isEmpty(vm)) return <EnvironmentFormSkeleton />;

return (
<>
Expand All @@ -75,18 +67,16 @@ const EnvironmentForm: FC<EnvironmentFormProps> = ({ onEditChange, updateVM, vm

{environments.map((environment, index) => (
<EnvironmentEditor
configMaps={configMaps}
diskName={environment.diskName}
environmentName={environment.name}
environmentNamesSelected={environmentNamesSelected}
id={index}
key={environment.name}
kind={environment.kind}
namespace={getNamespace(vm)}
onChange={onEnvironmentChange}
onRemove={onEnvironmentRemove}
secrets={secrets}
serial={environment?.serial}
serviceAccounts={serviceAccounts}
/>
))}

Expand All @@ -110,7 +100,7 @@ const EnvironmentForm: FC<EnvironmentFormProps> = ({ onEditChange, updateVM, vm
})
}
closeError={() => setFormError(null)}
error={loadError || formError}
error={formError}
isSaveDisabled={!edited || !environments.every((env) => env.name)}
onSave={() => updateVM(temporaryVM)}
/>
Expand Down
133 changes: 18 additions & 115 deletions src/utils/components/EnvironmentEditor/components/EnvironmentEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,151 +1,54 @@
import * as React from 'react';
import React, { FC } from 'react';

import {
IoK8sApiCoreV1ConfigMap,
IoK8sApiCoreV1Secret,
IoK8sApiCoreV1ServiceAccount,
} from '@kubevirt-ui/kubevirt-api/kubernetes/models';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { Button, Divider, TextInput, Tooltip } from '@patternfly/react-core';
import { Select, SelectGroup, SelectVariant } from '@patternfly/react-core/deprecated';
import { Button, TextInput, Tooltip } from '@patternfly/react-core';
import { MinusCircleIcon } from '@patternfly/react-icons';

import { EnvironmentKind, MapKindToAbbr } from '../constants';
import { EnvironmentOption } from '../utils';
import { EnvironmentKind } from '../constants';

import EnvironmentSelectOption from './EnvironmentSelectOption';
import EnvironmentSelectResource from './EnvironmentSelectResource';

import './EnvironmentEditor.scss';

type EnvironmentEditorProps = {
configMaps: IoK8sApiCoreV1ConfigMap[];
diskName: string;
environmentName?: string;
environmentNamesSelected: string[];
id: number;
kind?: EnvironmentKind;
namespace: string;
onChange: (diskName: string, name: string, serial: string, kind: EnvironmentKind) => void;
onRemove?: (diskName: string) => void;
secrets: IoK8sApiCoreV1Secret[];
serial?: string;
serviceAccounts: IoK8sApiCoreV1ServiceAccount[];
};

const EnvironmentEditor: React.FC<EnvironmentEditorProps> = ({
configMaps,
const EnvironmentEditor: FC<EnvironmentEditorProps> = ({
diskName,
environmentName,
environmentNamesSelected,
id,
kind,
namespace,
onChange,
onRemove,
secrets,
serial,
serviceAccounts,
}) => {
const { t } = useKubevirtTranslation();
const [isOpen, setOpen] = React.useState(false);

const onFilter = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>, value: string): React.ReactElement[] => {
const filteredSecrets = secrets
?.filter((secret) => secret.metadata.name.includes(value))
?.map((secret) => (
<EnvironmentSelectOption
isDisabled={environmentNamesSelected?.includes(secret.metadata.name)}
key={secret.metadata.name}
kind={EnvironmentKind.secret}
name={secret.metadata.name}
/>
));

const filteredConfigMaps = configMaps
?.filter((configMap) => configMap.metadata.name.includes(value))
?.map((configMap) => (
<EnvironmentSelectOption
isDisabled={environmentNamesSelected?.includes(configMap.metadata.name)}
key={configMap.metadata.name}
kind={EnvironmentKind.configMap}
name={configMap.metadata.name}
/>
));

const filteredServiceAccounts = serviceAccounts
?.filter((serviceAccount) => serviceAccount.metadata.name.includes(value))
?.map((serviceAccount) => (
<EnvironmentSelectOption
isDisabled={environmentNamesSelected?.includes(serviceAccount.metadata.name)}
key={serviceAccount.metadata.name}
kind={EnvironmentKind.serviceAccount}
name={serviceAccount.metadata.name}
/>
));

return [...filteredSecrets, ...filteredConfigMaps, ...filteredServiceAccounts];
},
[configMaps, environmentNamesSelected, secrets, serviceAccounts],
);

return (
<div className="row pairs-list__row">
<div className="col-xs-5 pairs-list__value-pair-field">
<Select
onSelect={(event, selection: EnvironmentOption) => {
onChange(diskName, selection.getName(), serial, selection.getKind());
setOpen(false);
}}
toggleIcon={
kind ? (
<span className={`co-m-resource-icon co-m-resource-${kind}`}>
{MapKindToAbbr[kind]}
</span>
) : null
}
aria-labelledby="environment-name-header"
hasInlineFilter
isOpen={isOpen}
maxHeight={400}
menuAppendTo="parent"
onFilter={onFilter}
onToggle={(_, isExpanded) => setOpen(isExpanded)}
placeholderText={t('Select a resource')}
selections={new EnvironmentOption(environmentName, kind)}
variant={SelectVariant.single}
>
<SelectGroup key="group1" label={t('Secrets')}>
{secrets.map((secret) => (
<EnvironmentSelectOption
isDisabled={environmentNamesSelected?.includes(secret.metadata.name)}
key={secret.metadata.name}
kind={EnvironmentKind.secret}
name={secret.metadata.name}
/>
))}
</SelectGroup>
<Divider key="divider1" />
<SelectGroup key="group2" label={t('Config Maps')}>
{configMaps.map((configMap) => (
<EnvironmentSelectOption
isDisabled={environmentNamesSelected?.includes(configMap.metadata.name)}
key={configMap.metadata.name}
kind={EnvironmentKind.configMap}
name={configMap.metadata.name}
/>
))}
</SelectGroup>
<Divider key="divider2" />
<SelectGroup key="group3" label={t('Service Accounts')}>
{serviceAccounts.map((serviceAccount) => (
<EnvironmentSelectOption
isDisabled={environmentNamesSelected?.includes(serviceAccount.metadata.name)}
key={serviceAccount.metadata.name}
kind={EnvironmentKind.serviceAccount}
name={serviceAccount.metadata.name}
/>
))}
</SelectGroup>
</Select>
<EnvironmentSelectResource
diskName={diskName}
environmentName={environmentName}
environmentNamesSelected={environmentNamesSelected}
kind={kind}
namespace={namespace}
onChange={onChange}
serial={serial}
/>
</div>

<div className="col-xs-5 pairs-list__name-field">
<TextInput
aria-labelledby="environment-serial-header"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import React, { ChangeEvent, FC, ReactElement, useCallback, useState } from 'react';

import Loading from '@kubevirt-utils/components/Loading/Loading';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { Alert, Divider } from '@patternfly/react-core';
import { Select, SelectGroup, SelectVariant } from '@patternfly/react-core/deprecated';

import { EnvironmentKind, MapKindToAbbr } from '../constants';
import useEnvironmentsResources from '../hooks/useEnvironmentsResources';
import { EnvironmentOption } from '../utils';

import EnvironmentSelectOption from './EnvironmentSelectOption';

type EnvironmentSelectResourceProps = {
diskName: string;
environmentName?: string;
environmentNamesSelected: string[];
kind?: EnvironmentKind;
namespace: string;
onChange: (diskName: string, name: string, serial: string, kind: EnvironmentKind) => void;
serial: string;
};

const EnvironmentSelectResource: FC<EnvironmentSelectResourceProps> = ({
diskName,
environmentName,
environmentNamesSelected,
kind,
namespace,
onChange,
serial,
}) => {
const { t } = useKubevirtTranslation();

const [isOpen, setOpen] = useState(false);

const {
configMaps,
error: loadError,
loaded,
secrets,
serviceAccounts,
} = useEnvironmentsResources(namespace);

const onFilter = useCallback(
(event: ChangeEvent<HTMLInputElement>, value: string): ReactElement[] => {
const filteredSecrets = secrets
?.filter((secret) => secret.metadata.name.includes(value))
?.map((secret) => (
<EnvironmentSelectOption
isDisabled={environmentNamesSelected?.includes(secret.metadata.name)}
key={secret.metadata.name}
kind={EnvironmentKind.secret}
name={secret.metadata.name}
/>
));

const filteredConfigMaps = configMaps
?.filter((configMap) => configMap.metadata.name.includes(value))
?.map((configMap) => (
<EnvironmentSelectOption
isDisabled={environmentNamesSelected?.includes(configMap.metadata.name)}
key={configMap.metadata.name}
kind={EnvironmentKind.configMap}
name={configMap.metadata.name}
/>
));

const filteredServiceAccounts = serviceAccounts
?.filter((serviceAccount) => serviceAccount.metadata.name.includes(value))
?.map((serviceAccount) => (
<EnvironmentSelectOption
isDisabled={environmentNamesSelected?.includes(serviceAccount.metadata.name)}
key={serviceAccount.metadata.name}
kind={EnvironmentKind.serviceAccount}
name={serviceAccount.metadata.name}
/>
));

return [...filteredSecrets, ...filteredConfigMaps, ...filteredServiceAccounts];
},
[configMaps, environmentNamesSelected, secrets, serviceAccounts],
);

if (!loaded) return <Loading />;

if (loadError)
return (
<Alert
className="co-alert co-alert--scrollable"
isInline
title={t('An error occurred')}
variant="danger"
>
<div className="co-pre-line">{loadError?.message}</div>
</Alert>
);

return (
<Select
onSelect={(event, selection: EnvironmentOption) => {
onChange(diskName, selection.getName(), serial, selection.getKind());
setOpen(false);
}}
toggleIcon={
kind ? (
<span className={`co-m-resource-icon co-m-resource-${kind}`}>{MapKindToAbbr[kind]}</span>
) : null
}
aria-labelledby="environment-name-header"
hasInlineFilter
isOpen={isOpen}
maxHeight={400}
menuAppendTo="parent"
onFilter={onFilter}
onToggle={(_, isExpanded) => setOpen(isExpanded)}
placeholderText={t('Select a resource')}
selections={new EnvironmentOption(environmentName, kind)}
variant={SelectVariant.single}
>
<SelectGroup key="group1" label={t('Secrets')}>
{secrets.map((secret) => (
<EnvironmentSelectOption
isDisabled={environmentNamesSelected?.includes(secret.metadata.name)}
key={secret.metadata.name}
kind={EnvironmentKind.secret}
name={secret.metadata.name}
/>
))}
</SelectGroup>
<Divider key="divider1" />
<SelectGroup key="group2" label={t('Config Maps')}>
{configMaps.map((configMap) => (
<EnvironmentSelectOption
isDisabled={environmentNamesSelected?.includes(configMap.metadata.name)}
key={configMap.metadata.name}
kind={EnvironmentKind.configMap}
name={configMap.metadata.name}
/>
))}
</SelectGroup>
<Divider key="divider2" />
<SelectGroup key="group3" label={t('Service Accounts')}>
{serviceAccounts.map((serviceAccount) => (
<EnvironmentSelectOption
isDisabled={environmentNamesSelected?.includes(serviceAccount.metadata.name)}
key={serviceAccount.metadata.name}
kind={EnvironmentKind.serviceAccount}
name={serviceAccount.metadata.name}
/>
))}
</SelectGroup>
</Select>
);
};

export default EnvironmentSelectResource;
Loading
Loading