Merge branch 'application-mgt-new' into 'application-mgt-new'

Improve APIM's edit release view and fix install/uninstall's timestamp issue

## Purpose
The purpose is to fix the following issues with this PR.
1. In the current APPM Publisher UI's edit release section, there is no way to preview selected images. Also, the supported versions are hardcoded.
2. There is an issue after adding a scheduled install/uninstall feature to the UI, which tries to send a query param with null timestamp value with non-scheduled operations.

## Goals
- Add previews of selected images to upload
- Add supported OS versions field
- Fix timestamp issue in scheduled install/uninstall operation in APPM UI

## Approach
- Used the same approach to add image previews and supported OS versions field which used in the "Add New Release" view in APPM publisher UI.
- Removed the inline function which caused the timestamp issue & created a new function to handle the operation.

## Documentation
> N/A

## Automation tests
> N/A

## Security checks
 - Followed secure coding standards? (yes)
 - Ran FindSecurityBugs plugin and verified report? (no)
 - Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets? (yes)

## Related MRs
> (Any other related Merge Requests)

## Test environment
> (Context and environment where development was performed)

## Learning
> (Any additional learning resources)

See merge request entgra/carbon-device-mgt!295
This commit is contained in:
Saad Sahibjan 2019-10-09 07:35:06 +00:00
commit 11c4d15518
18 changed files with 221 additions and 86 deletions

View File

@ -28,6 +28,13 @@ import {withConfigContext} from "../../../context/ConfigContext";
const {Title, Text, Paragraph} = Typography;
class ReleaseView extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
const {app, release} = this.props;
const config = this.props.context;
@ -88,8 +95,10 @@ class ReleaseView extends React.Component {
<EditRelease
isAppUpdatable={isAppUpdatable}
type={app.type}
deviceType={app.deviceType}
release={release}
updateRelease={this.props.updateRelease}
supportedOsVersions={[...this.props.supportedOsVersions]}
/>
</Col>

View File

@ -17,12 +17,28 @@
*/
import React from "react";
import {Modal, Button, Icon, notification, Spin, Tooltip, Upload, Input, Switch, Form, Divider, Row, Col} from 'antd';
import {
Modal,
Button,
Icon,
notification,
Spin,
Tooltip,
Upload,
Input,
Switch,
Form,
Divider,
Row,
Col,
Select
} from 'antd';
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
const {TextArea} = Input;
const InputGroup = Input.Group;
const {Option} = Select;
const formItemLayout = {
labelCol: {
@ -33,6 +49,15 @@ const formItemLayout = {
},
};
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
class EditReleaseModal extends React.Component {
constructor(props) {
@ -51,6 +76,8 @@ class EditReleaseModal extends React.Component {
specificElements: {}
}
};
this.lowerOsVersion = null;
this.upperOsVersion = null;
}
componentDidMount = () => {
@ -117,15 +144,16 @@ class EditReleaseModal extends React.Component {
showModal = () => {
const config = this.props.context;
const {app, release} = this.props;
const {formConfig} = this.state;
const {specificElements} = formConfig;
let metaData = [];
try{
metaData =JSON.parse(release.metaData);
}catch (e) {
try {
metaData = JSON.parse(release.metaData);
} catch (e) {
}
this.props.form.setFields({
@ -143,14 +171,19 @@ class EditReleaseModal extends React.Component {
}
});
// if (specificElements.hasOwnProperty("packageName")) {
// this.props.form.setFields({
// packageName: {
// value: app.packageName
// }
// });
// }
if ((config.deviceTypes.mobileTypes.includes(this.props.deviceType))) {
const osVersions = release.supportedOsVersions.split("-");
this.lowerOsVersion = osVersions[0];
this.upperOsVersion = osVersions[1];
this.props.form.setFields({
lowerOsVersion: {
value: osVersions[0]
},
upperOsVersion: {
value: osVersions[1]
}
});
}
if (specificElements.hasOwnProperty("version")) {
this.props.form.setFields({
version: {
@ -232,9 +265,12 @@ class EditReleaseModal extends React.Component {
isSharedWithAllTenants,
metaData: JSON.stringify(this.state.metaData),
releaseType: releaseType,
supportedOsVersions: "4-30"
};
if ((config.deviceTypes.mobileTypes.includes(this.props.deviceType))) {
release.supportedOsVersions = `${this.lowerOsVersion}-${this.upperOsVersion}`;
}
if (specificElements.hasOwnProperty("binaryFile") && binaryFiles.length === 1) {
data.append('binaryFile', binaryFiles[0].originFileObj);
}
@ -322,10 +358,50 @@ class EditReleaseModal extends React.Component {
})
};
handlePreviewCancel = () => this.setState({previewVisible: false});
handlePreview = async file => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
this.setState({
previewImage: file.url || file.preview,
previewVisible: true,
});
};
handleLowerOsVersionChange = (lowerOsVersion) => {
this.lowerOsVersion = lowerOsVersion;
};
handleUpperOsVersionChange = (upperOsVersion) => {
this.upperOsVersion = upperOsVersion;
};
render() {
const {formConfig, icons, screenshots, loading, binaryFiles, metaData} = this.state;
const {
formConfig,
icons,
screenshots,
loading,
binaryFiles,
metaData,
previewImage,
previewVisible,
binaryFileHelperText,
iconHelperText,
screenshotHelperText
} = this.state;
const {getFieldDecorator} = this.props.form;
const {isAppUpdatable} = this.props;
const {isAppUpdatable, supportedOsVersions, deviceType} = this.props;
const config = this.props.context;
const uploadButton = (
<div>
<Icon type="plus"/>
<div className="ant-upload-text">Select</div>
</div>
);
return (
<div>
@ -340,8 +416,8 @@ class EditReleaseModal extends React.Component {
title="Edit release"
visible={this.state.visible}
footer={null}
onCancel={this.handleCancel}
>
width={580}
onCancel={this.handleCancel}>
<div>
<Spin tip="Uploading..." spinning={loading}>
<Form labelAlign="left" layout="horizontal"
@ -370,19 +446,6 @@ class EditReleaseModal extends React.Component {
</Form.Item>
)}
{/*{formConfig.specificElements.hasOwnProperty("packageName") && (*/}
{/* <Form.Item {...formItemLayout} label="Package Name">*/}
{/* {getFieldDecorator('packageName', {*/}
{/* rules: [{*/}
{/* required: true,*/}
{/* message: 'Please input the package name'*/}
{/* }],*/}
{/* })(*/}
{/* <Input placeholder="Package Name"/>*/}
{/* )}*/}
{/* </Form.Item>*/}
{/*)}*/}
{formConfig.specificElements.hasOwnProperty("url") && (
<Form.Item {...formItemLayout} label="URL">
{getFieldDecorator('url', {
@ -418,19 +481,15 @@ class EditReleaseModal extends React.Component {
})(
<Upload
name="logo"
listType="picture-card"
onChange={this.handleIconChange}
beforeUpload={() => false}
>
{icons.length !== 1 && (
<Button>
<Icon type="upload"/> Change
</Button>
)}
onPreview={this.handlePreview}>
{icons.length === 1 ? null : uploadButton}
</Upload>,
)}
</Form.Item>
<Form.Item {...formItemLayout} label="Screenshots">
{getFieldDecorator('screenshots', {
valuePropName: 'icon',
@ -440,15 +499,11 @@ class EditReleaseModal extends React.Component {
})(
<Upload
name="screenshots"
listType="picture-card"
onChange={this.handleScreenshotChange}
beforeUpload={() => false}
multiple
>
{screenshots.length < 3 && (
<Button>
<Icon type="upload"/> Click to upload
</Button>
)}
onPreview={this.handlePreview}>
{screenshots.length >= 3 ? null : uploadButton}
</Upload>,
)}
</Form.Item>
@ -475,7 +530,65 @@ class EditReleaseModal extends React.Component {
rows={5}/>
)}
</Form.Item>
{(config.deviceTypes.mobileTypes.includes(deviceType)) && (
<Form.Item {...formItemLayout} label="Supported OS Versions">
{getFieldDecorator('supportedOS')(
<div>
<InputGroup>
<Row gutter={8}>
<Col span={11}>
<Form.Item>
{getFieldDecorator('lowerOsVersion', {
rules: [{
required: true,
message: 'Please select Value'
}],
})(
<Select
placeholder="Lower version"
style={{width: "100%"}}
onChange={this.handleLowerOsVersionChange}>
{supportedOsVersions.map(version => (
<Option key={version.versionName}
value={version.versionName}>
{version.versionName}
</Option>
))}
</Select>
)}
</Form.Item>
</Col>
<Col span={2}>
<p> - </p>
</Col>
<Col span={11}>
<Form.Item>
{getFieldDecorator('upperOsVersion', {
rules: [{
required: true,
message: 'Please select Value'
}],
})(
<Select style={{width: "100%"}}
placeholder="Upper version"
onChange={this.handleUpperOsVersionChange}>
{supportedOsVersions.map(version => (
<Option key={version.versionName}
value={version.versionName}>
{version.versionName}
</Option>
))}
</Select>
)}
</Form.Item>
</Col>
</Row>
</InputGroup>
</div>
)}
</Form.Item>
)}
<Form.Item {...formItemLayout} label="Price">
{getFieldDecorator('price', {
rules: [{
@ -575,6 +688,9 @@ class EditReleaseModal extends React.Component {
</Form>
</Spin>
</div>
<Modal visible={previewVisible} footer={null} onCancel={this.handlePreviewCancel}>
<img alt="Preview Image" style={{width: '100%'}} src={previewImage}/>
</Modal>
</Modal>
</div>
);

View File

@ -37,7 +37,6 @@ class LifeCycleDetailsModal extends React.Component {
};
handleCancel = e => {
console.log(e);
this.setState({
visible: false,
});

View File

@ -39,7 +39,6 @@ class AddNewPage extends React.Component {
handleCancel = e => {
console.log(e);
this.setState({
visible: false,
});

View File

@ -37,14 +37,12 @@ class GooglePlayIframe extends React.Component {
};
handleOk = e => {
console.log(e);
this.setState({
visible: false,
});
};
handleCancel = e => {
console.log(e);
this.setState({
visible: false,
});

View File

@ -43,14 +43,12 @@ class ManagedConfigurationsIframe extends React.Component {
};
handleOk = e => {
console.log(e);
this.setState({
visible: false,
});
};
handleCancel = e => {
console.log(e);
this.setState({
visible: false,
});
@ -116,7 +114,6 @@ class ManagedConfigurationsIframe extends React.Component {
updateConfig = (method, event) => {
const {packageName} = this.props;
this.setState({loading: true});
console.log(event);
const data = {
mcmId: event.mcmId,
@ -151,7 +148,6 @@ class ManagedConfigurationsIframe extends React.Component {
deleteConfig = (event) => {
const {packageName} = this.props;
this.setState({loading: true});
console.log(event);
//send request to the invoker
axios.delete(

View File

@ -47,7 +47,6 @@ class Pages extends React.Component {
rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
// console.lohhhg(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
this.setState({
selectedRows: selectedRows
})

View File

@ -84,9 +84,6 @@ class NewAppUploadForm extends React.Component {
this.setState({
loading: true
});
console.log(values);
const {price, isSharedWithAllTenants, binaryFile, icon, screenshots, releaseDescription, releaseType} = values;
//add release data
@ -234,8 +231,7 @@ class NewAppUploadForm extends React.Component {
<Form
labelAlign="right"
layout="horizontal"
onSubmit={this.handleSubmit}
>
onSubmit={this.handleSubmit}>
{formConfig.specificElements.hasOwnProperty("binaryFile") && (
<Form.Item {...formItemLayout}
label="Application"
@ -250,8 +246,7 @@ class NewAppUploadForm extends React.Component {
<Upload
name="binaryFile"
onChange={this.handleBinaryFileChange}
beforeUpload={() => false}
>
beforeUpload={() => false}>
{binaryFiles.length !== 1 && (
<Button>
<Icon type="upload"/> Click to upload
@ -277,8 +272,7 @@ class NewAppUploadForm extends React.Component {
listType="picture-card"
onChange={this.handleIconChange}
beforeUpload={() => false}
onPreview={this.handlePreview}
>
onPreview={this.handlePreview}>
{icons.length === 1 ? null : uploadButton}
</Upload>,
)}
@ -418,7 +412,6 @@ class NewAppUploadForm extends React.Component {
</Select>,
)}
</Form.Item>
<Form.Item {...formItemLayout} label="Price">
{getFieldDecorator('price', {
rules: [{
@ -437,7 +430,6 @@ class NewAppUploadForm extends React.Component {
/>
)}
</Form.Item>
<Form.Item {...formItemLayout} label="Is Shared?">
{getFieldDecorator('isSharedWithAllTenants', {
rules: [{
@ -450,7 +442,6 @@ class NewAppUploadForm extends React.Component {
unCheckedChildren={<Icon type="close"/>}
/>
)}
</Form.Item>
<Form.Item {...formItemLayout} label="Meta Data">
{getFieldDecorator('meta', {
@ -510,7 +501,6 @@ class NewAppUploadForm extends React.Component {
</Button>
</div>
)}
</Form.Item>
<Form.Item style={{float: "right", marginLeft: 8}}>
<Button type="primary" htmlType="submit">
@ -531,7 +521,6 @@ class NewAppUploadForm extends React.Component {
</div>
);
}
}
export default (Form.create({name: 'app-upload-form'})(NewAppUploadForm));

View File

@ -39,7 +39,8 @@ class Release extends React.Component {
uuid: null,
release: null,
currentLifecycleStatus: null,
lifecycle: null
lifecycle: null,
supportedOsVersions:[]
};
}
@ -85,14 +86,17 @@ class Release extends React.Component {
loading: false,
uuid: uuid
});
if(config.deviceTypes.mobileTypes.includes(app.deviceType)){
this.getSupportedOsVersions(app.deviceType);
}else{
this.getLifecycle();
}
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load the release.");
this.setState({loading: false});
});
this.getLifecycle();
};
getLifecycle = () => {
@ -112,6 +116,28 @@ class Release extends React.Component {
});
};
getSupportedOsVersions = (deviceType) => {
const config = this.props.context;
axios.get(
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt +
`/admin/device-types/${deviceType}/versions`
).then(res => {
if (res.status === 200) {
let supportedOsVersions = JSON.parse(res.data.data);
this.setState({
supportedOsVersions,
loading: false,
});
this.getLifecycle();
}
}).catch((error) => {
handleApiError(error, "Error occurred while trying to load supported OS versions.");
this.setState({
loading: false
});
});
};
render() {
const {app, release, currentLifecycleStatus, lifecycle, loading} = this.state;
@ -138,6 +164,7 @@ class Release extends React.Component {
currentLifecycleStatus={currentLifecycleStatus}
lifecycle={lifecycle}
updateRelease={this.updateRelease}
supportedOsVersions = {[...this.state.supportedOsVersions]}
/>)
}
</Skeleton>

View File

@ -62,16 +62,16 @@ class ReleaseView extends React.Component {
headers: {'X-Platform': config.serverConfig.platform}
}
).then(res => {
if (res.status === 200) {
if (res.status === 200 || res.status === 201) {
this.setState({
loading: false,
appInstallModalVisible: false,
appUnInstallModalVisible: false,
appUninstallModalVisible: false,
});
notification["success"]({
message: 'Done!',
description:
'App '+operation+'ed triggered.',
'Operation triggered.',
});
} else {
this.setState({

View File

@ -193,7 +193,7 @@ class DeviceUninstall extends React.Component {
type: device.type
});
});
this.props.onUninstall("devices", payload, "uninstall", null);
this.props.onUninstall("devices", payload, "uninstall", timestamp);
};
render() {

View File

@ -88,7 +88,7 @@ class GroupUninstall extends React.Component {
value.map(val=>{
data.push(val.key);
});
this.props.onUninstall("group", data, "uninstall",null);
this.props.onUninstall("group", data, "uninstall",timestamp);
};
render() {

View File

@ -87,7 +87,7 @@ class RoleUninstall extends React.Component {
value.map(val=>{
data.push(val.key);
});
this.props.onUninstall("role", data, "uninstall",null);
this.props.onUninstall("role", data, "uninstall",timestamp);
};
render() {

View File

@ -86,7 +86,7 @@ class UserUninstall extends React.Component {
value.map(val => {
data.push(val.key);
});
this.props.onUninstall("user", data, "uninstall",null);
this.props.onUninstall("user", data, "uninstall",timestamp);
};
render() {

View File

@ -46,6 +46,14 @@ class InstallModalFooter extends React.Component{
})
};
triggerInstallOperation = () =>{
this.props.operation();
};
triggerScheduledInstallOperation = () =>{
const {scheduledTime} =this.state;
this.props.operation(scheduledTime);
};
render() {
const {scheduledTime,isScheduledInstallVisible} =this.state;
const {disabled, type} = this.props;
@ -56,7 +64,7 @@ class InstallModalFooter extends React.Component{
display: (!isScheduledInstallVisible)?'block':'none'
}}>
<Button style={{margin: 5}} disabled={disabled} htmlType="button" type="primary"
onClick={this.props.operation}>
onClick={this.triggerInstallOperation}>
{type}
</Button>
<Button style={{margin: 5}} disabled={disabled} htmlType="button"
@ -76,9 +84,7 @@ class InstallModalFooter extends React.Component{
style={{margin: 5}}
htmlType="button"
type="primary"
onClick={()=>{
this.props.operation(scheduledTime);
}}>
onClick={this.triggerScheduledInstallOperation}>
Schedule
</Button>
<Button style={{margin: 5}} htmlType="button"

View File

@ -41,7 +41,6 @@ class EditReview extends React.Component {
componentDidMount() {
const {content,rating,id} = this.props.review;
console.log(this.props.review);
this.setState({
content,
rating

View File

@ -19,7 +19,6 @@
import {notification} from "antd";
export const handleApiError = (error, message) => {
console.log(error);
if (error.hasOwnProperty("response") && error.response.status === 401) {
const redirectUrl = encodeURI(window.location.href);
window.location.href = window.location.origin + `/store/login?redirect=${redirectUrl}`;

View File

@ -80,7 +80,6 @@ class NormalLoginForm extends React.Component {
handleSubmit = (e) => {
const thisForm = this;
const config = this.props.context;
console.log(config);
e.preventDefault();
this.props.form.validateFields((err, values) => {