diff --git a/sass/_components.scss b/sass/_components.scss index 10dfe7397..feda0aaf7 100644 --- a/sass/_components.scss +++ b/sass/_components.scss @@ -8,14 +8,12 @@ @import './components/Form/dropdown'; @import './components/Form/form-group'; @import './components/Form/list-form-factory'; -@import './components/HostStatus/host-status'; @import './components/Table/editable-table-actions'; @import './components/Table/editable-table'; @import './components/TemplateSource/template-source'; @import './components/VmConsoles/desktop-viewer-selector'; @import './components/VmDetails/boot-order'; @import './components/VmDetails/vm-details'; -@import './components/VmStatus/vm-status'; @import './components/Wizard/create-vm-wizard'; @import './components/Wizard/wizard'; @import './components/ConfigurationSummary/configuration-summary'; @@ -35,6 +33,8 @@ @import './components/CreateBaremetalHostDialog/create-baremetal-host-dialog'; @import './components/StorageOverview/data-resiliency'; @import './components/StorageOverview/top-consumer'; +@import './components/Status/status'; +@import './components/HostStatus/host-status'; /* TODO: these styles should be backported to the corresponding PF-React package diff --git a/sass/components/HostStatus/host-status.scss b/sass/components/HostStatus/host-status.scss index 166ae1efb..635c86e63 100644 --- a/sass/components/HostStatus/host-status.scss +++ b/sass/components/HostStatus/host-status.scss @@ -1,3 +1,7 @@ -.kubevirt-host-status__icon { - margin-right: 5px; +.kubevirt-host-status__description { + margin-bottom: 1em; +} + +.kubevirt-host-status__reason { + margin-bottom: 1em; } diff --git a/sass/components/Status/status.scss b/sass/components/Status/status.scss new file mode 100644 index 000000000..dc1d2cad8 --- /dev/null +++ b/sass/components/Status/status.scss @@ -0,0 +1,8 @@ +.kubevirt-status__icon { + margin-right: 5px; +} + +.kubevirt-status__button { + padding: 0; + margin: 0; +} diff --git a/sass/components/VmStatus/vm-status.scss b/sass/components/VmStatus/vm-status.scss deleted file mode 100644 index 07918ecf3..000000000 --- a/sass/components/VmStatus/vm-status.scss +++ /dev/null @@ -1,3 +0,0 @@ -.kubevirt-vm-status__icon { - margin-right: 5px; -} diff --git a/src/components/BareMetalHosts/StatusComponents.js b/src/components/BareMetalHosts/StatusComponents.js index 944351940..4aea7d2a6 100644 --- a/src/components/BareMetalHosts/StatusComponents.js +++ b/src/components/BareMetalHosts/StatusComponents.js @@ -1,19 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Popover } from '@patternfly/react-core'; -import { Icon } from 'patternfly-react'; -export const IconAndText = ({ icon, text }) => ( - - - {text} - -); - -IconAndText.propTypes = { - icon: PropTypes.string.isRequired, - text: PropTypes.string.isRequired, -}; +import { Status, OverlayStatus } from '../Status'; // Generic status component as a fallback export const GenericStatus = ({ status, text, errorMessage }) => text; @@ -29,28 +18,40 @@ GenericStatus.defaultProps = { }; // Generic success status component -export const GenericSuccess = ({ status, text, errorMessage }) => ; +export const GenericSuccess = ({ status, text, errorMessage }) => {text}; GenericSuccess.propTypes = GenericStatus.propTypes; GenericSuccess.defaultProps = GenericStatus.defaultProps; +const ErrorOverlay = ({ errorMessage }) => ( + +
{errorMessage}
+
+); + +ErrorOverlay.propTypes = { + errorMessage: PropTypes.string, +}; + +ErrorOverlay.defaultProps = { + errorMessage: null, +}; + // Generic error status component // If the errorMessage property isn't empty its contents are // shown in a Popover. export const GenericError = ({ status, text, errorMessage }) => errorMessage ? ( - Errors} bodyContent={errorMessage}> - - + } /> ) : ( - + {text} ); GenericError.propTypes = GenericStatus.propTypes; GenericError.defaultProps = GenericStatus.defaultProps; // Generic progress status component -export const GenericProgress = ({ status, text, errorMessage }) => ; +export const GenericProgress = ({ status, text, errorMessage }) => {text}; GenericProgress.propTypes = GenericStatus.propTypes; GenericProgress.defaultProps = GenericStatus.defaultProps; @@ -58,9 +59,7 @@ GenericProgress.defaultProps = GenericStatus.defaultProps; // Validation Error component // (TODO) Add details for validation errors in Popover component export const ValidationError = ({ status, text, errorMessage }) => ( - Errors} bodyContent={errorMessage}> - - + } /> ); ValidationError.propTypes = { @@ -71,6 +70,6 @@ ValidationError.propTypes = { export const AddDiscoveredHostLink = host => ( - + Add host ); diff --git a/src/components/BareMetalHosts/tests/__snapshots__/StatusComponents.test.js.snap b/src/components/BareMetalHosts/tests/__snapshots__/StatusComponents.test.js.snap index b2c69c5db..c62b8f54e 100644 --- a/src/components/BareMetalHosts/tests/__snapshots__/StatusComponents.test.js.snap +++ b/src/components/BareMetalHosts/tests/__snapshots__/StatusComponents.test.js.snap @@ -2,96 +2,60 @@ exports[` renders a link to add discovered host 1`] = ` - + > + Add host + `; exports[` renders an error message 1`] = ` - +> + Registration Error + `; exports[` with details renders an error message with details 1`] = ` - - Errors - + } - hideOnOutsideClick={true} - isVisible={null} - onHidden={[Function]} - onHide={[Function]} - onMount={[Function]} - onShow={[Function]} - onShown={[Function]} - position="right" - shouldClose={[Function]} - size="regular" - zIndex={9999} -> - - + text="Registration Error" +/> `; exports[` renders a progress message 1`] = ` - +> + Provisioning + `; exports[` renders a generic status string 1`] = `"Provisioned"`; exports[` renders a success message 1`] = ` - +> + Provisioned + `; exports[` renders a validation error 1`] = ` - - Errors - + } - hideOnOutsideClick={true} - isVisible={null} - onHidden={[Function]} - onHide={[Function]} - onMount={[Function]} - onShow={[Function]} - onShown={[Function]} - position="right" - shouldClose={[Function]} - size="regular" - zIndex={9999} -> - - + text="Validation Error(s)" +/> `; diff --git a/src/components/Details/VmDetails/tests/__snapshots__/VmDetails.test.js.snap b/src/components/Details/VmDetails/tests/__snapshots__/VmDetails.test.js.snap index d1d24a56f..9ab766e36 100644 --- a/src/components/Details/VmDetails/tests/__snapshots__/VmDetails.test.js.snap +++ b/src/components/Details/VmDetails/tests/__snapshots__/VmDetails.test.js.snap @@ -62,7 +62,7 @@ Array [
@@ -251,7 +251,7 @@ Array [
@@ -450,7 +450,7 @@ Array [
@@ -655,7 +655,7 @@ Array [
@@ -862,7 +862,7 @@ Array [
@@ -1037,7 +1037,7 @@ exports[` renders correctly as overview 1`] = `
@@ -1243,7 +1243,7 @@ Array [
@@ -1430,7 +1430,7 @@ Array [
@@ -1629,7 +1629,7 @@ Array [
@@ -1818,7 +1818,7 @@ Array [
@@ -2021,7 +2021,7 @@ Array [
@@ -2228,7 +2228,7 @@ Array [
diff --git a/src/components/NodeStatus/NodeStatus.js b/src/components/NodeStatus/NodeStatus.js new file mode 100644 index 000000000..17350d49b --- /dev/null +++ b/src/components/NodeStatus/NodeStatus.js @@ -0,0 +1,106 @@ +import React from 'react'; +import { Popover } from 'patternfly-react'; +import PropTypes from 'prop-types'; + +import { + getNodeStatus, + NODE_STATUS_UNDER_MAINTENANCE, + NODE_STATUS_STOPPING_MAINTENANCE, +} from '../../utils/status/node'; +import { getCreationTimestamp, getDeletionTimestamp, getName, getMaintenanceReason } from '../../selectors'; +import { OverlayStatus } from '../Status'; + +const UnderMaintenanceStatus = ({ node, maintenance, TimestampComponent }) => { + const maintenanceReason = getMaintenanceReason(maintenance); + const created = getCreationTimestamp(maintenance); + const overlay = ( + +
This host is under maintenance.
+ {maintenanceReason && ( + + Maintenance reason: +
{maintenanceReason}
+
+ )} +
+ Started: +
+
+ ); + + return ; +}; + +UnderMaintenanceStatus.defaultProps = { + maintenance: null, +}; + +UnderMaintenanceStatus.propTypes = { + node: PropTypes.object.isRequired, + maintenance: PropTypes.object, + TimestampComponent: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired, +}; + +const StoppingMaintenanceStatus = ({ node, maintenance, TimestampComponent }) => { + const created = getCreationTimestamp(maintenance); + const deleted = getDeletionTimestamp(maintenance); + const overlay = ( + +
+ This host is leaving maintenance. It will rejoin the cluster and resume accepting workloads. +
+
+ Started: +
+
+ Ended: +
+
+ ); + + return ; +}; + +StoppingMaintenanceStatus.defaultProps = { + maintenance: null, +}; + +StoppingMaintenanceStatus.propTypes = { + node: PropTypes.object.isRequired, + maintenance: PropTypes.object, + TimestampComponent: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired, +}; + +export const NodeStatus = ({ node, maintenances, TimestampComponent }) => { + const nodeStatus = getNodeStatus(node, maintenances); + switch (nodeStatus.status) { + case NODE_STATUS_UNDER_MAINTENANCE: + return ( + + ); + case NODE_STATUS_STOPPING_MAINTENANCE: + return ( + + ); + default: + return null; + } +}; + +NodeStatus.defaultProps = { + maintenances: null, +}; + +NodeStatus.propTypes = { + node: PropTypes.object.isRequired, + maintenances: PropTypes.array, + TimestampComponent: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired, +}; diff --git a/src/components/NodeStatus/fixtures/NodeStatus.fixture.js b/src/components/NodeStatus/fixtures/NodeStatus.fixture.js new file mode 100644 index 000000000..6f84c6ed9 --- /dev/null +++ b/src/components/NodeStatus/fixtures/NodeStatus.fixture.js @@ -0,0 +1,64 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { NodeStatus } from '../NodeStatus'; +import { NodeMaintenance } from '../../../models'; + +const maintenance = { + kind: NodeMaintenance.kind, + apiVersion: `${NodeMaintenance.apiGroup}/${NodeMaintenance.apiVersion}`, + metadata: { + name: 'nodemaintenance-fooNodeName', + creationTimestamp: '2019-04-25T18:49:13Z', + }, + spec: { + nodeName: 'fooNodeName', + reason: 'some reason', + }, +}; + +const maintenanceWithDeletionTimestamp = { + kind: NodeMaintenance.kind, + apiVersion: `${NodeMaintenance.apiGroup}/${NodeMaintenance.apiVersion}`, + metadata: { + name: 'nodemaintenance-fooNodeName', + creationTimestamp: '2019-04-25T18:49:13Z', + deletionTimestamp: '2019-04-26T18:49:13Z', + }, + spec: { + nodeName: 'fooNodeName', + reason: 'some reason', + }, +}; + +const node = { + metadata: { + name: 'fooNodeName', + }, +}; + +const Timestamp = ({ timestamp }) => {timestamp}; +Timestamp.propTypes = { + timestamp: PropTypes.string.isRequired, +}; + +export default [ + { + component: NodeStatus, + name: 'Under Maintenance', + props: { + node, + maintenances: [maintenance], + TimestampComponent: Timestamp, + }, + }, + { + component: NodeStatus, + name: 'Stopping Maintenance', + props: { + node, + maintenances: [maintenanceWithDeletionTimestamp], + TimestampComponent: Timestamp, + }, + }, +]; diff --git a/src/components/NodeStatus/index.js b/src/components/NodeStatus/index.js new file mode 100644 index 000000000..466aff853 --- /dev/null +++ b/src/components/NodeStatus/index.js @@ -0,0 +1 @@ +export * from './NodeStatus'; diff --git a/src/components/NodeStatus/tests/NodeStatus.test.js b/src/components/NodeStatus/tests/NodeStatus.test.js new file mode 100644 index 000000000..b8acf02ab --- /dev/null +++ b/src/components/NodeStatus/tests/NodeStatus.test.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import { NodeStatus } from '../NodeStatus'; +import NodeStatusFixtures from '../fixtures/NodeStatus.fixture'; + +const testNodeStatus = () => ; + +describe('', () => { + it('renders correctly', () => { + const component = shallow(testNodeStatus()); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/components/NodeStatus/tests/__snapshots__/NodeStatus.test.js.snap b/src/components/NodeStatus/tests/__snapshots__/NodeStatus.test.js.snap new file mode 100644 index 000000000..81cf96bbf --- /dev/null +++ b/src/components/NodeStatus/tests/__snapshots__/NodeStatus.test.js.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders correctly 1`] = ` + +`; diff --git a/src/components/Status/Status.js b/src/components/Status/Status.js new file mode 100644 index 000000000..a104ccb18 --- /dev/null +++ b/src/components/Status/Status.js @@ -0,0 +1,64 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { Icon, OverlayTrigger, Button } from 'patternfly-react'; +import PropTypes from 'prop-types'; + +export const Status = ({ icon, children }) => ( + + {icon && } + {children} + +); + +Status.defaultProps = { + icon: null, +}; + +Status.propTypes = { + icon: PropTypes.string, + children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired, +}; + +export const OverlayStatus = ({ icon, text, overlay }) => ( + + + + + +); + +OverlayStatus.defaultProps = { + icon: null, +}; + +OverlayStatus.propTypes = { + icon: PropTypes.string, + text: PropTypes.string.isRequired, + overlay: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired, +}; + +export const LinkStatus = ({ icon, children, linkMessage, linkTo }) => + linkTo ? ( + + + {children} + + + ) : ( + {children} + ); + +LinkStatus.defaultProps = { + icon: null, + linkMessage: null, + linkTo: null, +}; + +LinkStatus.propTypes = { + icon: PropTypes.string, + linkMessage: PropTypes.string, + linkTo: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), + children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired, +}; diff --git a/src/components/Status/fixtures/Status.fixture.js b/src/components/Status/fixtures/Status.fixture.js new file mode 100644 index 000000000..b6aef78fc --- /dev/null +++ b/src/components/Status/fixtures/Status.fixture.js @@ -0,0 +1,28 @@ +import React from 'react'; +import { Popover } from 'patternfly-react'; + +import { Status, OverlayStatus } from '../Status'; + +export default [ + { + component: Status, + name: 'Off status', + props: { + icon: 'off', + children: 'off status', + }, + }, + { + component: OverlayStatus, + name: 'Status with overlay', + props: { + icon: 'off', + text: 'status text', + overlay: ( + +
some content
+
+ ), + }, + }, +]; diff --git a/src/components/Status/index.js b/src/components/Status/index.js new file mode 100644 index 000000000..b230b08b9 --- /dev/null +++ b/src/components/Status/index.js @@ -0,0 +1 @@ +export * from './Status'; diff --git a/src/components/Status/tests/Status.test.js b/src/components/Status/tests/Status.test.js new file mode 100644 index 000000000..de9db5cd0 --- /dev/null +++ b/src/components/Status/tests/Status.test.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import { Status } from '../Status'; +import StatusFixtures from '../fixtures/Status.fixture'; + +const testStatus = () => ; + +describe('', () => { + it('renders correctly', () => { + const component = shallow(testStatus()); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/components/Status/tests/__snapshots__/Status.test.js.snap b/src/components/Status/tests/__snapshots__/Status.test.js.snap new file mode 100644 index 000000000..5b515e3c8 --- /dev/null +++ b/src/components/Status/tests/__snapshots__/Status.test.js.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders correctly 1`] = ` + + + off status + +`; diff --git a/src/components/VmStatus/VmStatus.js b/src/components/VmStatus/VmStatus.js index 18eb45245..8ae524266 100644 --- a/src/components/VmStatus/VmStatus.js +++ b/src/components/VmStatus/VmStatus.js @@ -1,6 +1,5 @@ -import React, { Fragment } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; -import { Link } from 'react-router-dom'; import { CDI_KUBEVIRT_IO, STORAGE_IMPORT_PVC_NAME } from '../../constants'; @@ -21,62 +20,39 @@ import { getVmStatus, } from '../../utils/status/vm'; import { getId, getVmImporterPods } from '../../selectors'; +import { Status, LinkStatus } from '../Status'; const getAdditionalImportText = pod => ` (${pod.metadata.labels[`${CDI_KUBEVIRT_IO}/${STORAGE_IMPORT_PVC_NAME}`]})`; -const StateValue = ({ iconClass, children, linkTo, message }) => ( - - -); -StateValue.propTypes = { - children: PropTypes.any, - iconClass: PropTypes.string.isRequired, - linkTo: PropTypes.string, - message: PropTypes.string, -}; -StateValue.defaultProps = { - children: null, - linkTo: undefined, - message: undefined, -}; - const StateRunning = ({ ...props }) => ( - + Running - + ); -const StateOff = () => Off; -const StateUnknown = () => Unknown; -const StateMigrating = () => Migrating; +const StateOff = () => Off; +const StateUnknown = () => Unknown; +const StateMigrating = () => Migrating; const StateVmiWaiting = ({ ...props }) => ( - + Pending - + ); const StateStarting = ({ ...props }) => ( - + Starting - + ); const StateImporting = ({ additionalText, ...props }) => ( - + Importing {additionalText} - + ); const StateV2VConversionInProgress = ({ progress, ...props }) => ( - + V2V Conversion In Progress - + ); StateV2VConversionInProgress.defaultProps = { @@ -87,9 +63,9 @@ StateV2VConversionInProgress.propTypes = { }; const StateV2VConversionError = ({ ...props }) => ( - + V2V Conversion Error - + ); StateImporting.defaultProps = { additionalText: undefined, @@ -98,9 +74,9 @@ StateImporting.propTypes = { additionalText: PropTypes.string, }; const StateError = ({ children, ...props }) => ( - + {children} - + ); StateError.propTypes = { children: PropTypes.any.isRequired, diff --git a/src/components/VmStatus/tests/__snapshots__/VmStatus.test.js.snap b/src/components/VmStatus/tests/__snapshots__/VmStatus.test.js.snap index 3c86c3130..2e4e19833 100644 --- a/src/components/VmStatus/tests/__snapshots__/VmStatus.test.js.snap +++ b/src/components/VmStatus/tests/__snapshots__/VmStatus.test.js.snap @@ -4,7 +4,7 @@ exports[` renders correctly 1`] = ` Array [