Skip to content

Commit

Permalink
add affinity-modal
Browse files Browse the repository at this point in the history
  • Loading branch information
Gilad Lekner authored and Gilad Lekner committed Apr 5, 2020
1 parent 2888129 commit dc5918b
Show file tree
Hide file tree
Showing 31 changed files with 1,400 additions and 142 deletions.
@@ -1,6 +1,9 @@
import { IDEntity } from '../../types';
import { MatchExpression } from '@console/internal/module/k8s';

export type IDLabel = IDEntity & {
key: string;
value: string;
value?: string;
values?: string[];
operator?: MatchExpression['operator'];
};
@@ -0,0 +1,3 @@
.affinity-modal__add-btn {
padding: var(--pf-global--spacer--xl);
}
@@ -0,0 +1,198 @@
import * as React from 'react';
import * as _ from 'lodash';
import {
withHandlePromise,
HandlePromiseProps,
FirehoseResult,
} from '@console/internal/components/utils';
import { Button, ButtonVariant, Split, SplitItem } from '@patternfly/react-core';
import { ModalTitle, ModalBody, ModalComponentProps } from '@console/internal/components/factory';
import { NodeModel } from '@console/internal/models';
import { K8sResourceKind, k8sPatch } from '@console/internal/module/k8s';
import { VMLikeEntityKind } from '../../../../types/vmLike';
import { getVMLikeModel } from '../../../../selectors/vm';
import { getVMLikeAffinity } from '../../../../selectors/vm-like/selectors';
import { getLoadedData, isLoaded, getLoadError } from '../../../../utils';
import { useCollisionChecker } from '../../../../hooks/use-collision-checker';
import { ModalFooter } from '../../modal/modal-footer';
import { AFFINITY_MODAL_TITLE, AFFINITY_CREATE, AFFINITY_EDITING } from '../shared/consts';
import { AffinityTable } from './components/affinity-table/affinity-table';
import { AffinityRow } from './components/affinity-table/affinity-row';
import { AffinityEdit } from './components/affinity-edit';
import { AffinityRowData } from './types';
import {
getRowsDataFromAffinity,
getAffinityFromRowsData,
defaultNewAffinity,
columnClasses,
} from './helpers';
import { getAffinityPatch } from '../../../../k8s/patches/vm/vm-scheduling-patches';
import './affinity-modal.scss';

export const AffinityModal = withHandlePromise<AffinityModalProps>(
({
vmLikeEntity,
vmLikeEntityLoading,
nodes,
close,
handlePromise,
inProgress,
errorMessage,
}) => {
const vmLikeFinal = getLoadedData(vmLikeEntityLoading, vmLikeEntity);
const loadError = getLoadError(nodes, NodeModel);
const currentAffinity = getVMLikeAffinity(vmLikeFinal);

const [affinities, setAffinities] = React.useState<AffinityRowData[]>(
getRowsDataFromAffinity(currentAffinity),
);
const [focusedAffinity, setFocusedAffinity] = React.useState<AffinityRowData>(
defaultNewAffinity,
);

const [isEditing, setIsEditing] = React.useState(false);
const [isCreating, setIsCreating] = React.useState(false);
const [showCollisionAlert, reload] = useCollisionChecker<VMLikeEntityKind>(
vmLikeFinal,
(oldVM: VMLikeEntityKind, newVM: VMLikeEntityKind) =>
_.isEqual(getVMLikeAffinity(oldVM), getVMLikeAffinity(newVM)),
);

const onReload = () => {
reload();
setAffinities(getRowsDataFromAffinity(currentAffinity));
setIsCreating(false);
};

const onAffinityAdd = (affinity: AffinityRowData) => {
setAffinities([...affinities, affinity]);
setIsEditing(false);
setIsCreating(false);
};

const onAffinityChange = (updatedAffinity: AffinityRowData) => {
setAffinities(
affinities.map((affinity) => {
if (affinity.id === updatedAffinity.id) return { ...affinity, ...updatedAffinity };
return affinity;
}),
);
setIsEditing(false);
};

const onAffinityClickAdd = () => {
setIsEditing(true);
setIsCreating(true);
setFocusedAffinity(defaultNewAffinity);
};

const onAffinityClickEdit = (affinity: AffinityRowData) => {
setFocusedAffinity(affinity);
setIsEditing(true);
};

const onAffinityDelete = (affinity: AffinityRowData) =>
setAffinities(affinities.filter(({ id }) => id !== affinity.id));

const submit = async () => {
if (!_.isEqual(affinities, getRowsDataFromAffinity(currentAffinity))) {
// eslint-disable-next-line promise/catch-or-return
handlePromise(
k8sPatch(
getVMLikeModel(vmLikeFinal),
vmLikeFinal,
await getAffinityPatch(vmLikeFinal, getAffinityFromRowsData(affinities)),
),
).then(close);
} else {
close();
}
};

const onCancel = () => {
if (isEditing) {
setIsEditing(false);
setIsCreating(false);
} else {
close();
}
};

const modalTitle = !isEditing
? AFFINITY_MODAL_TITLE
: isCreating
? AFFINITY_CREATE
: AFFINITY_EDITING;

return (
<div className="modal-content">
<Split>
<SplitItem>
<ModalTitle>{modalTitle}</ModalTitle>
</SplitItem>
<SplitItem isFilled />
<SplitItem className="affinity-modal__add-btn">
{!isEditing && (
<Button onClick={() => onAffinityClickAdd()} variant="secondary">
Add Affinity
</Button>
)}
</SplitItem>
</Split>
{!isEditing ? (
<ModalBody>
<AffinityTable
columnClasses={columnClasses}
data={affinities}
customData={{
isDisabled: false,
vmLikeFinal,
onEdit: onAffinityClickEdit,
onDelete: onAffinityDelete,
}}
row={AffinityRow}
/>
</ModalBody>
) : (
<AffinityEdit
nodes={nodes}
affinity={focusedAffinity}
affinities={affinities}
onAffinitySubmit={isCreating ? onAffinityAdd : onAffinityChange}
onCancel={onCancel}
/>
)}
{!isEditing && (
<ModalFooter
id="affinity"
className="kubevirt-affinity__footer"
errorMessage={errorMessage}
inProgress={!isLoaded(nodes) || inProgress}
isSimpleError={!!loadError}
onSubmit={submit}
onCancel={onCancel}
submitButtonText={'Apply'}
infoTitle={showCollisionAlert && 'Affinity has been updated outside this flow.'}
infoMessage={
<>
Saving these changes will override any Affinity previously saved.
<br />
<Button variant={ButtonVariant.link} isInline onClick={onReload}>
Reload Affinity
</Button>
.
</>
}
/>
)}
</div>
);
},
);

type AffinityModalProps = HandlePromiseProps &
ModalComponentProps & {
vmLikeEntity: VMLikeEntityKind;
nodes?: FirehoseResult<K8sResourceKind[]>;
vmLikeEntityLoading?: FirehoseResult<VMLikeEntityKind>;
};

0 comments on commit dc5918b

Please sign in to comment.