Skip to content
This repository has been archived by the owner on Aug 28, 2023. It is now read-only.

Add modal for uploading/resetting manifest in UI #36

Merged
merged 10 commits into from
Jan 28, 2020
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 34 additions & 13 deletions app/javascript/react/screens/App/Analytics/Analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import AnalyticsEmptyState from './screens/AnalyticsEmptyState';
import AnalyticsSummary from './screens/AnalyticsSummary';
import AnalyticsProviderSelection from './screens/AnalyticsProviderSelection';
import AnalyticsDataCollection from './screens/AnalyticsDataCollection';
import ManifestVersion from './components/ManifestVersion';
// import BreadcrumbPageSwitcher from '../common/BreadcrumbPageSwitcher'; // TODO: figure out how to share the breadcrumb switcher with v2v

const SCREENS = {
Expand Down Expand Up @@ -41,7 +42,15 @@ class Analytics extends React.Component {
};

render() {
const { manifestInfo } = this.props;
const {
manifestInfo,
isLoadingManifest,
manifestError,
toggleManifestUpdateModalAction,
manifestUpdateModalVisible,
uploadManifestAction,
resetManifestAction
} = this.props;
const { currentScreen } = this.state;
const onEmptyState = currentScreen === SCREENS.EMPTY_STATE;
return (
Expand All @@ -58,29 +67,41 @@ class Analytics extends React.Component {
<div id="migration-analytics" className={onEmptyState ? 'row cards-pf' : ''}>
{this.renderCurrentScreen()}
</div>
{manifestInfo && (
<h6 id="migration-analytics-manifest-version" className={onEmptyState ? 'on-empty-state' : ''}>
{__('Manifest version:')}
&nbsp;
{manifestInfo.manifest_version}
</h6>
)}
<ManifestVersion
className={onEmptyState ? 'on-empty-state' : ''}
manifestInfo={manifestInfo}
isLoadingManifest={isLoadingManifest}
manifestError={manifestError}
toggleManifestUpdateModalAction={toggleManifestUpdateModalAction}
manifestUpdateModalVisible={manifestUpdateModalVisible}
uploadManifestAction={uploadManifestAction}
resetManifestAction={resetManifestAction}
/>
</React.Fragment>
);
}
}

Analytics.propTypes = {
fetchManifestInfoAction: PropTypes.func,
manifestInfo: PropTypes.shape({
manifest_version: PropTypes.string,
using_default_manifest: PropTypes.bool
})
manifestInfo: PropTypes.object,
toggleManifestUpdateModalAction: PropTypes.func,
manifestUpdateModalVisible: PropTypes.bool,
uploadManifestAction: PropTypes.func,
resetManifestAction: PropTypes.func,
isLoadingManifest: PropTypes.bool,
manifestError: PropTypes.shape({ message: PropTypes.string })
};

Analytics.defaultProps = {
fetchManifestInfoAction: noop,
manifestInfo: null
manifestInfo: null,
toggleManifestUpdateModalAction: noop,
manifestUpdateModalVisible: false,
uploadManifestAction: noop,
resetManifestAction: noop,
isLoadingManifest: false,
manifestError: null
};

export default Analytics;
26 changes: 26 additions & 0 deletions app/javascript/react/screens/App/Analytics/Analytics.scss
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,35 @@
right: 0;
margin: 0;
padding: 15px;
text-align: right;
background-color: #ffffff;

&.on-empty-state {
background-color: $color-pf-black-150;
}

> button.btn-link {
margin-bottom: 2px;
}

.manifest-loading-message,
.manifest-error-message {
display: block;
padding: 5px;

> .spinner-xs {
position: relative;
top: 2px;
margin-left: 5px;
}

> .pficon-error-circle-o {
margin-right: 2px;
}
}
}

.manifest-update-modal .col-xs-6 {
padding-left: 5px;
padding-right: 5px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,15 @@ exports[`Analytics root component renders toolbar and empty state correctly 1`]
onGetStartedClick={[Function]}
/>
</div>
<ManifestVersion
className="on-empty-state"
isLoadingManifest={false}
manifestError={null}
manifestInfo={null}
manifestUpdateModalVisible={false}
resetManifestAction={[Function]}
toggleManifestUpdateModalAction={[Function]}
uploadManifestAction={[Function]}
/>
</Fragment>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Modal, Grid, Button, noop } from 'patternfly-react';
import Dropzone from 'react-dropzone';
import { readFirstFile } from './helpers';

const ManifestUpdateModal = ({ onClose, manifestInfo, uploadManifestAction, resetManifestAction, ...otherProps }) => (
<Modal {...otherProps} onHide={onClose} backdrop="static" className="manifest-update-modal">
<Dropzone onDrop={files => readFirstFile(files, file => uploadManifestAction(file.body))}>
{({ getRootProps, getInputProps, open: openFileBrowser }) => (
<div {...getRootProps()} onClick={event => event.preventDefault()}>
<input {...getInputProps()} />
<Modal.Header>
<Modal.CloseButton onClick={onClose} />
<Modal.Title>{__('Update Manifest')}</Modal.Title>
</Modal.Header>
<Modal.Body>
<Grid.Row className="show-grid">
<Grid.Col xs={6} className="text-right">
{__('Current manifest version')}:
</Grid.Col>
<Grid.Col xs={6}>{manifestInfo.manifest_version}</Grid.Col>
</Grid.Row>
<Grid.Row className="show-grid">
<Grid.Col xs={6} className="text-right">
{__('Default manifest version')}:
</Grid.Col>
<Grid.Col xs={6}>{manifestInfo.default_manifest_version}</Grid.Col>
</Grid.Row>
</Modal.Body>
<Modal.Footer>
<Button bsStyle="default" onClick={resetManifestAction} disabled={manifestInfo.using_default_manifest}>
{__('Restore default manifest')}
</Button>
<Button bsStyle="default" onClick={openFileBrowser}>
{__('Upload new manifest')}
</Button>
<Button bsStyle="primary" className="btn-cancel" onClick={onClose}>
{__('Close')}
</Button>
</Modal.Footer>
</div>
)}
</Dropzone>
</Modal>
);

ManifestUpdateModal.propTypes = {
onClose: PropTypes.func,
manifestInfo: PropTypes.shape({
manifest_version: PropTypes.string,
default_manifest_version: PropTypes.string,
using_default_manifest: PropTypes.bool
}),
uploadManifestAction: PropTypes.func,
resetManifestAction: PropTypes.func
};

ManifestUpdateModal.defaultProps = {
onClose: noop,
manifestInfo: {},
uploadManifestAction: noop,
resetManifestAction: noop
};

export default ManifestUpdateModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Icon, Spinner, noop } from 'patternfly-react';
import ManifestUpdateModal from './ManifestUpdateModal';

const ManifestVersion = ({
manifestInfo,
isLoadingManifest,
manifestError,
manifestUpdateModalVisible,
toggleManifestUpdateModalAction,
uploadManifestAction,
resetManifestAction,
...otherProps
}) =>
manifestInfo && (
<React.Fragment>
<h6 id="migration-analytics-manifest-version" {...otherProps}>
{!isLoadingManifest ? (
<React.Fragment>
{__('Manifest version:')}
&nbsp;
{manifestInfo.manifest_version}
&nbsp;
<Button bsStyle="link" bsSize="xsmall" onClick={() => toggleManifestUpdateModalAction(true)}>
{__('Update')}
</Button>
</React.Fragment>
) : (
<span className="manifest-loading-message">
{__('Loading manifest...')}
<Spinner inline size="xs" loading />
</span>
)}
{manifestError && manifestError.message && (
<span className="manifest-error-message">
<Icon type="pf" name="error-circle-o" size="sm" /> {manifestError.message}
</span>
)}
</h6>
<ManifestUpdateModal
show={manifestUpdateModalVisible}
onClose={() => toggleManifestUpdateModalAction(false)}
manifestInfo={manifestInfo}
uploadManifestAction={uploadManifestAction}
resetManifestAction={resetManifestAction}
/>
</React.Fragment>
);

ManifestVersion.propTypes = {
manifestInfo: PropTypes.shape({
manifest_version: PropTypes.string
}),
isLoadingManifest: PropTypes.bool,
manifestError: PropTypes.shape({ message: PropTypes.string }),
manifestUpdateModalVisible: PropTypes.bool,
toggleManifestUpdateModalAction: PropTypes.func,
uploadManifestAction: PropTypes.func,
resetManifestAction: PropTypes.func
};

ManifestVersion.defaultProps = {
manifestInfo: null,
isLoadingManifest: false,
manifestError: null,
manifestUpdateModalVisible: false,
toggleManifestUpdateModalAction: noop,
uploadManifestAction: noop,
resetManifestAction: noop
};

export default ManifestVersion;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const readFirstFile = (fileHandles, onLoad) => {
if (fileHandles && fileHandles.length > 0) {
const reader = new FileReader();
reader.onload = () => onLoad({ filename: fileHandles[0].name, body: reader.result });
reader.readAsBinaryString(fileHandles[0]);
}
};
25 changes: 21 additions & 4 deletions app/javascript/react/screens/App/Analytics/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import { connect } from 'react-redux';
import Analytics from './Analytics';
import { fetchManifestInfoAction } from './redux/analyticsActions';
import {
fetchManifestInfoAction,
toggleManifestUpdateModalAction,
uploadManifestAction,
resetManifestAction
} from './redux/analyticsActions';

const mapStateToProps = ({
migrationAnalytics: {
analytics: { manifestInfo }
analytics: {
manifestInfo,
manifestUpdateModalVisible,
isFetchingManifestInfo,
isChangingManifest,
errorFetchingManifestInfo,
errorChangingManifest
}
}
}) => ({ manifestInfo });
}) => ({
manifestInfo,
manifestUpdateModalVisible,
isLoadingManifest: isFetchingManifestInfo || isChangingManifest,
manifestError: errorFetchingManifestInfo || errorChangingManifest
});

export default connect(
mapStateToProps,
{ fetchManifestInfoAction }
{ fetchManifestInfoAction, toggleManifestUpdateModalAction, uploadManifestAction, resetManifestAction }
)(Analytics);
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ exports[`analytics reducer constructs initial state correctly 1`] = `
Object {
"bundleStartResult": null,
"bundleTask": null,
"changeManifestResult": null,
"detailedDataSelected": false,
"errorChangingManifest": null,
"errorFetchingBundleTask": null,
"errorFetchingManifestInfo": null,
"errorStartingBundle": null,
"isChangingManifest": false,
"isFetchingBundleTask": false,
"isFetchingManifestInfo": false,
"isStartingBundle": false,
"manifestInfo": null,
"manifestUpdateModalVisible": false,
"selectedProviders": Array [],
"summaryData": null,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,53 @@ import {
SELECT_DETAILED_DATA,
FETCH_MANIFEST_INFO,
MANIFEST_INFO_URL,
TOGGLE_MANIFEST_UPDATE_MODAL,
START_INVENTORY_BUNDLE,
INVENTORY_BUNDLE_URL,
FETCH_BUNDLE_TASK,
RESET_DATA_COLLECTION_STATE
RESET_DATA_COLLECTION_STATE,
CHANGE_MANIFEST
} from './constants';
import { simpleActionWithProperties, basicFetchAction } from '../../../../../redux/helpers';

export const fetchManifestInfoAction = () => basicFetchAction(FETCH_MANIFEST_INFO, MANIFEST_INFO_URL);

export const toggleManifestUpdateModalAction = (show = null) => dispatch =>
dispatch({ type: TOGGLE_MANIFEST_UPDATE_MODAL, show });

export const uploadManifestAction = fileBody => dispatch => {
toggleManifestUpdateModalAction(false)(dispatch);
try {
const manifest = JSON.parse(fileBody);
if (!manifest.cfme_version || !manifest.manifest || !manifest.manifest.version) {
throw new Error();
}
dispatch({
type: CHANGE_MANIFEST,
payload: API.post(new URI(MANIFEST_INFO_URL).toString(), {
action: 'import_manifest',
manifest
})
}).then(() => fetchManifestInfoAction()(dispatch));
} catch (e) {
// If JSON.parse fails or the error above is thrown
dispatch({
type: `${CHANGE_MANIFEST}_REJECTED`,
payload: { data: { error: { message: 'Selected file is not a valid manifest' } } }
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that the Selected file is not a valid manifest string should be enclosed in gettext as well.

});
}
};

export const resetManifestAction = () => dispatch => {
toggleManifestUpdateModalAction(false)(dispatch);
dispatch({
type: CHANGE_MANIFEST,
payload: API.post(new URI(MANIFEST_INFO_URL).toString(), {
action: 'reset_manifest'
})
}).then(() => fetchManifestInfoAction()(dispatch));
};

export const calculateSummaryDataAction = results => simpleActionWithProperties(CALCULATE_SUMMARY_DATA, { results });

export const selectProvidersAction = selectedProviders =>
Expand Down