Skip to content
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
24 changes: 14 additions & 10 deletions frontend/__tests__/components/factory/details.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
import * as React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { Provider } from 'react-redux';
import { mount, ReactWrapper } from 'enzyme';

import { DetailsPage, DetailsPageProps } from '../../../public/components/factory/details';
import { PodModel, ConfigMapModel } from '../../../public/models';
import { referenceForModel } from '../../../public/module/k8s';
import { Firehose } from '../../../public/components/utils';
import store from '@console/internal/redux';
import { DetailsPage, DetailsPageProps } from '@console/internal/components/factory/details';
import { PodModel, ConfigMapModel } from '@console/internal/models';
import { referenceForModel } from '@console/internal/module/k8s';
import { Firehose } from '@console/internal/components/utils';

describe(DetailsPage.displayName, () => {
let wrapper: ShallowWrapper<DetailsPageProps>;
let wrapper: ReactWrapper<DetailsPageProps>;

beforeEach(() => {
const match = { params: { ns: 'default' }, isExact: true, path: '', url: '' };

wrapper = shallow(
// Need full mount with redux store since this is a redux-connected component
wrapper = mount(
<DetailsPage
match={match}
name="test-name"
namespace="default"
kind={referenceForModel(PodModel)}
pages={[]}
/>,
)
.childAt(0)
.shallow();
{
wrappingComponent: ({ children }) => <Provider store={store}>{children}</Provider>,
Copy link
Contributor

Choose a reason for hiding this comment

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

@spadgett @christianvogt FYI this seems to be the recommended way to test Redux-connected components.

https://airbnb.io/enzyme/docs/api/ShallowWrapper/getWrappingComponent.html

},
);
});

it('renders a `Firehose` using the given props', () => {
Expand Down
17 changes: 10 additions & 7 deletions frontend/__tests__/components/graphs/prometheus-graph.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,16 @@ describe('<PrometheusGraphLink />', () => {
// Need full mount with redux store since this is a redux-connected component
const getWrapper = (query: string) => {
const wrapper = mount(
<Router history={history}>
<Provider store={store}>
<PrometheusGraphLink query={query}>
<p className="test-class" />
</PrometheusGraphLink>
</Provider>
</Router>,
<PrometheusGraphLink query={query}>
<p className="test-class" />
</PrometheusGraphLink>,
{
wrappingComponent: ({ children }) => (
<Router history={history}>
<Provider store={store}>{children}</Provider>
</Router>
),
},
);
expect(wrapper.find('p.test-class').exists()).toBe(true);
return wrapper;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ describe(CopyToClipboard.displayName, () => {
.find<any>(CTC)
.props()
.onCopy();
wrapper.update();

// re-render component created via React.memo
wrapper.setProps({ value: 'FuzzBizz' });
Copy link
Contributor

Choose a reason for hiding this comment

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


expect(wrapper.find(Tooltip).props().content[0].props.children).toEqual('Copied');
});
Expand All @@ -28,7 +30,6 @@ describe(CopyToClipboard.displayName, () => {
.find<any>(CTC)
.props()
.onCopy();
wrapper.update();

wrapper.find('.co-copy-to-clipboard__btn').simulate('mouseenter');

Expand Down
6 changes: 3 additions & 3 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@
},
"devDependencies": {
"@types/classnames": "^2.2.7",
"@types/enzyme": "3.x",
"@types/enzyme": "3.10.x",
"@types/glob": "7.x",
"@types/immutable": "3.x",
"@types/jasmine": "2.8.x",
Expand All @@ -164,8 +164,8 @@
"chromedriver": "77.x",
"circular-dependency-plugin": "5.0.2",
"css-loader": "0.28.x",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "1.12.1",
"enzyme": "3.10.x",
"enzyme-adapter-react-16": "1.15.2",
"file-loader": "1.x",
"find-up": "4.x",
"fork-ts-checker-webpack-plugin": "0.x",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.ceph-restore-pvc-modal__details {
Copy link
Member

Choose a reason for hiding this comment

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

nit: alpha (details before details-section)

display: flex;
flex-direction: row;
justify-content: space-between;
}

.ceph-restore-pvc-modal__details-section {
margin-top: var(--pf-global--spacer--xl);
}

.ceph-restore-pvc-modal__pvc-details {
font-weight: 600;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import './_restore-pvc-modal.scss';

import * as React from 'react';

import { Form, FormGroup, Grid, GridItem, TextInput } from '@patternfly/react-core';
import {
HandlePromiseProps,
ResourceIcon,
withHandlePromise,
} from '@console/internal/components/utils/index';
import { K8sResourceKind, k8sCreate, k8sGet } from '@console/internal/module/k8s';
import {
ModalBody,
ModalComponentProps,
ModalSubmitFooter,
ModalTitle,
createModalLauncher,
} from '@console/internal/components/factory';
import { NamespaceModel, PersistentVolumeClaimModel } from '@console/internal/models';
import { getName, getNamespace } from '@console/shared';

import { VolumeSnapshotModel } from '../../../models';

export const RestorePVCModal = withHandlePromise((props: RestorePVCModalProps) => {
const { close, cancel, resource, errorMessage, inProgress, handlePromise } = props;
const [pvcResource, setResource] = React.useState(null);
const [restorePVCName, setPVCName] = React.useState(`${getName(resource) || 'pvc'}-restore`);

React.useEffect(() => {
k8sGet(PersistentVolumeClaimModel, resource?.spec?.source?.name, getNamespace(resource))
.then(setResource)
.catch((error) => {
setResource(null);
throw error;
});
}, [resource]);

const submit = (event: React.FormEvent<EventTarget>) => {
event.preventDefault();
const snapshotName = getName(resource);
const pvcSize = resource?.status?.restoreSize;
const accessModes = pvcResource?.spec?.accessModes;
const pvcStorageClass = pvcResource?.spec?.storageClassName;
const namespace = getNamespace(resource);
const restorePVCTemplate: K8sResourceKind = {
apiVersion: PersistentVolumeClaimModel.apiVersion,
kind: PersistentVolumeClaimModel.kind,
metadata: {
name: restorePVCName,
},
spec: {
storageClassName: pvcStorageClass,
dataSource: {
name: snapshotName,
kind: VolumeSnapshotModel.kind,
apiGroup: 'snapshot.storage.k8s.io',
},
accessModes: [...accessModes],
resources: {
requests: {
storage: pvcSize,
},
},
},
};
handlePromise(k8sCreate(PersistentVolumeClaimModel, restorePVCTemplate, { ns: namespace }))
.then(close)
.catch((error) => {
throw error;
Copy link
Contributor

Choose a reason for hiding this comment

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

we don't need to throw it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to throw error to tell the user what went wrong.

});
};

return (
<div className="modal-content modal-content--no-inner-scroll">
<ModalTitle>Restore</ModalTitle>
<ModalBody>
<p>After restore action is finished, a new PVC will be created.</p>
<Form onSubmit={submit}>
<FormGroup label="Name" isRequired fieldId="restore-pvc-modal__name">
<TextInput
isRequired
type="text"
id="restore-pvc-modal__name"
name="restore-pvc-modal__name"
value={restorePVCName}
onChange={setPVCName}
/>
</FormGroup>
<div className="ceph-restore-pvc-modal__details-section">
<Grid gutter="md">
<GridItem span={6}>
<div>
<p className="ceph-restore-pvc-modal__pvc-details">Date</p>
<p>{resource?.metadata?.creationTimestamp}</p>
</div>
<div>
<p className="ceph-restore-pvc-modal__pvc-details">Status</p>
<p>{resource?.status?.readyToUse ? 'Ready' : 'Not Ready'}</p>
</div>
<div>
<p className="ceph-restore-pvc-modal__pvc-details">Size</p>
<p>{resource?.status?.restoreSize || 'No Data'}</p>
</div>
</GridItem>
<GridItem span={6}>
<div>
<p className="ceph-restore-pvc-modal__pvc-details">Namespace</p>
<p>
<ResourceIcon kind={NamespaceModel.kind} />
{getNamespace(resource)}
</p>
</div>
<div>
<p className="ceph-restore-pvc-modal__pvc-details">API Version</p>
<p>{resource?.apiVersion}</p>
</div>
<div>
<p className="ceph-restore-pvc-modal__pvc-details">Persistent Volume</p>
<ResourceIcon kind={PersistentVolumeClaimModel.kind} />
{resource?.spec?.source?.name}
</div>
</GridItem>
</Grid>
</div>
</Form>
</ModalBody>
<ModalSubmitFooter
inProgress={inProgress}
errorMessage={errorMessage}
submitText="Restore"
cancel={cancel}
/>
</div>
);
});

export type RestorePVCModalProps = {
resource: K8sResourceKind;
} & HandlePromiseProps &
ModalComponentProps;

export default createModalLauncher(RestorePVCModal);
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import * as React from 'react';

import {
Dropdown,
Firehose,
FirehoseResourcesResult,
HandlePromiseProps,
withHandlePromise,
} from '@console/internal/components/utils';
import { Form, FormGroup, TextInput } from '@patternfly/react-core';
import { K8sResourceKind, K8sResourceKindReference, k8sCreate } from '@console/internal/module/k8s';
import {
ModalBody,
ModalComponentProps,
ModalSubmitFooter,
ModalTitle,
createModalLauncher,
} from '@console/internal/components/factory';
import { getName, getNamespace } from '@console/shared';

import { PersistentVolumeClaimModel } from '@console/internal/models';
import { VolumeSnapshotModel } from '../../../models';

export type VolumeSnapshotModalProps = {
pvcData?: FirehoseResourcesResult;
} & HandlePromiseProps &
ModalComponentProps;

export const VolumeSnapshotModal = withHandlePromise((props: VolumeSnapshotModalProps) => {
const { close, cancel, pvcData, errorMessage, inProgress, handlePromise } = props;
const resource = pvcData.data as K8sResourceKind;
const [snapshotName, setSnapshotName] = React.useState(
`${getName(resource) || 'pvc'}-snapshot-1`,
);
const snapshotTypes = {
single: 'Single Snapshot',
};

const submit = (event: React.FormEvent<EventTarget>) => {
event.preventDefault();
const ns = getNamespace(resource);
const pvcName = getName(resource);
const snapshotTemplate: K8sResourceKind = {
apiVersion: VolumeSnapshotModel.apiVersion,
kind: VolumeSnapshotModel.kind,
metadata: {
name: snapshotName,
namespace: ns,
},
spec: {
source: {
name: pvcName,
kind: PersistentVolumeClaimModel.kind,
},
},
};
handlePromise(k8sCreate(VolumeSnapshotModel, snapshotTemplate))
.then(close)
.catch((error) => {
throw error;
Copy link
Contributor

Choose a reason for hiding this comment

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

not required

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to throw error to tell the user what went wrong.

});
};

return (
<Form onSubmit={submit}>
<div className="modal-content modal-content--no-inner-scroll">
<ModalTitle>Create Snapshot</ModalTitle>
<ModalBody>
<p>Creating snapshot for {getName(resource)}</p>
<FormGroup label="Name" isRequired fieldId="snapshot-name">
<TextInput
isRequired
type="text"
name="snapshot-name"
aria-label="snapshot-name"
value={snapshotName}
onChange={setSnapshotName}
/>
</FormGroup>
<FormGroup label="Schedule" fieldId="snapshot-modal__schedule">
<Dropdown
dropDownClassName="dropdown--full-width"
items={snapshotTypes}
selectedKey={snapshotTypes.single}
/>
</FormGroup>
</ModalBody>
<ModalSubmitFooter
inProgress={inProgress}
errorMessage={errorMessage}
submitText="Create"
cancel={cancel}
/>
</div>
</Form>
);
});

type VolumeSnapshotModalWithFireHoseProps = {
name: string;
namespace: string;
kind: K8sResourceKindReference;
pvcData?: FirehoseResourcesResult;
resource?: K8sResourceKind;
} & ModalComponentProps;

const VolumeSnapshotModalWithFireHose: React.FC<VolumeSnapshotModalWithFireHoseProps> = (props) => (
<Firehose
resources={[
{
kind: props.kind || PersistentVolumeClaimModel.kind,
prop: 'pvcData',
namespace: props?.resource?.metadata?.namespace || props.namespace,
isList: false,
name: props?.resource?.metadata?.name || props.name,
},
]}
>
<VolumeSnapshotModal {...props} />
</Firehose>
);

export const volumeSnapshotModal = createModalLauncher(VolumeSnapshotModalWithFireHose);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.ceph-volume-snapshot__header {
border-bottom: 1px solid #ddd;
padding: 10px 30px;
}

.ceph-volume-snapshot__header-buttons {
align-items: baseline;
display: flex;
}
Loading