Add UI Improvements to APPM UI

The following changes are with this commit
- Add metadata to Add new app & edit app forms in Publisher
- Display metadata in both publisher and store views
- Fix typo in lifecycle component
- Update table when changing the name & categories in apps table in publisher
feature/appm-store/pbac
Jayasanka 5 years ago
parent c91e1c43e7
commit 6786ed0fab

@ -200,6 +200,7 @@ class AppDetailsDrawer extends React.Component {
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/" + id,
data
).then(res => {
this.props.onUpdateApp("name", name);
if (res.status === 200) {
notification["success"]({
message: 'Saved!',
@ -282,6 +283,7 @@ class AppDetailsDrawer extends React.Component {
data
).then(res => {
if (res.status === 200) {
this.props.onUpdateApp("categories", temporaryCategories);
notification["success"]({
message: 'Saved!',
description: 'App categories updated successfully!'

@ -29,27 +29,10 @@ class ListApps extends React.Component {
constructor(props) {
super(props);
this.state = {
isDrawerVisible: false,
selectedApp: null,
filters: {}
}
}
//handler to show app drawer
showDrawer = (app) => {
this.setState({
isDrawerVisible: true,
selectedApp: app
});
};
// handler to close the app drawer
closeDrawer = () => {
this.setState({
isDrawerVisible: false
})
};
setFilters = (filters) => {
this.setState({
filters
@ -71,7 +54,6 @@ class ListApps extends React.Component {
render() {
const {isDrawerVisible, filters} = this.state;
return (
<Card>
<Row gutter={28}>
<Col md={6}>
@ -91,9 +73,7 @@ class ListApps extends React.Component {
</Col>
</Row>
<Divider dashed={true}/>
<AppsTable filters={filters} showDrawer={this.showDrawer}/>
<AppDetailsDrawer visible={isDrawerVisible} onClose={this.closeDrawer}
app={this.state.selectedApp}/>
<AppsTable filters={filters}/>
</Col>
</Row>
</Card>

@ -17,11 +17,12 @@
*/
import React from "react";
import {Avatar, Table, Tag, Icon, message, notification} from "antd";
import {Avatar, Table, Tag, Icon, message, notification, Col} from "antd";
import axios from "axios";
import pSBC from 'shade-blend-color';
import "./AppsTable.css";
import {withConfigContext} from "../../../../context/ConfigContext";
import AppDetailsDrawer from "../AppDetailsDrawer/AppDetailsDrawer";
let config = null;
@ -95,8 +96,11 @@ const columns = [
color = defaultPlatformIcons[platform].color;
theme = defaultPlatformIcons[platform].theme;
}
return (<span style={{fontSize: 20, color: color, textAlign: "center"}}><Icon type={icon}
theme={theme}/></span>)
return (
<span style={{fontSize: 20, color: color, textAlign: "center"}}>
<Icon type={icon} theme={theme}/>
</span>
);
}
},
{
@ -115,7 +119,10 @@ class AppsTable extends React.Component {
this.state = {
pagination: {},
apps: [],
filters: {}
filters: {},
isDrawerVisible: false,
selectedApp: null,
selectedAppIndex: -1
};
config = this.props.context;
}
@ -139,6 +146,22 @@ class AppsTable extends React.Component {
}
}
//handler to show app drawer
showDrawer = (app, appIndex) => {
this.setState({
isDrawerVisible: true,
selectedApp: app,
selectedAppIndex: appIndex
});
};
// handler to close the app drawer
closeDrawer = () => {
this.setState({
isDrawerVisible: false
})
};
handleTableChange = (pagination, filters, sorter) => {
const pager = {...this.state.pagination};
pager.current = pagination.current;
@ -189,9 +212,7 @@ class AppsTable extends React.Component {
apps: apps,
pagination,
});
}
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
message.error('You are not logged in');
@ -209,7 +230,16 @@ class AppsTable extends React.Component {
});
};
onUpdateApp = (key, value) => {
const apps = [...this.state.apps];
apps[this.state.selectedAppIndex][key]= value;
this.setState({
apps
});
};
render() {
const {isDrawerVisible} = this.state;
return (
<div className="apps-table">
<Table
@ -222,11 +252,15 @@ class AppsTable extends React.Component {
onRow={(record, rowIndex) => {
return {
onClick: event => {
this.props.showDrawer(record);
this.showDrawer(record, rowIndex);
},
};
}}
/>
}}/>
<AppDetailsDrawer
visible={isDrawerVisible}
onClose={this.closeDrawer}
app={this.state.selectedApp}
onUpdateApp={this.onUpdateApp}/>
</div>
);

@ -17,7 +17,7 @@
*/
import React from "react";
import {Divider, Row, Col, Typography, Button, Drawer, Icon, Tooltip} from "antd";
import {Divider, Row, Col, Typography, Button, Drawer, Icon, Tooltip, Empty} from "antd";
import StarRatings from "react-star-ratings";
import Reviews from "./review/Reviews";
import "../../../App.css";
@ -50,6 +50,12 @@ class ReleaseView extends React.Component {
color = defaultPlatformIcons[platform].color;
theme = defaultPlatformIcons[platform].theme;
}
let metaData = [];
try{
metaData = JSON.parse(release.metaData);
}catch (e) {
}
return (
<div>
@ -121,6 +127,21 @@ class ReleaseView extends React.Component {
{release.description}
</Paragraph>
<Divider/>
<Text>META DATA</Text>
<Row>
{
metaData.map((data, index)=>{
return (
<Col key={index} lg={8} md={6} xs={24} style={{marginTop:15}}>
<Text>{data.key}</Text><br/>
<Text type="secondary">{data.value}</Text>
</Col>
)
})
}
{(metaData.length===0) && (<Text type="secondary">No meta data available.</Text>)}
</Row>
<Divider/>
<Text>REVIEWS</Text>
<Row>
<Col lg={18}>

@ -17,11 +17,12 @@
*/
import React from "react";
import {Modal, Button, Icon, notification, Spin, Tooltip, Upload, Input, Switch, Form, Divider} from 'antd';
import {Modal, Button, Icon, notification, Spin, Tooltip, Upload, Input, Switch, Form, Divider, Row, Col} from 'antd';
import axios from "axios";
import {withConfigContext} from "../../../../context/ConfigContext";
const {TextArea} = Input;
const InputGroup = Input.Group;
const formItemLayout = {
labelCol: {
@ -45,6 +46,7 @@ class EditReleaseModal extends React.Component {
screenshots: [],
loading: false,
binaryFiles: [],
metaData: [],
formConfig: {
specificElements: {}
}
@ -118,6 +120,13 @@ class EditReleaseModal extends React.Component {
const {release} = this.props;
const {formConfig} = this.state;
const {specificElements} = formConfig;
let metaData = [];
try{
metaData =JSON.parse(release.metaData);
}catch (e) {
}
this.props.form.setFields({
releaseType: {
@ -160,6 +169,7 @@ class EditReleaseModal extends React.Component {
this.setState({
visible: true,
metaData
});
};
@ -212,7 +222,7 @@ class EditReleaseModal extends React.Component {
description: releaseDescription,
price: (price === undefined) ? 0 : parseInt(price),
isSharedWithAllTenants,
metaData: "string",
metaData: JSON.stringify(this.state.metaData),
releaseType: releaseType,
supportedOsVersions: "4.0-10.0"
};
@ -298,9 +308,14 @@ class EditReleaseModal extends React.Component {
});
};
addNewMetaData = () => {
this.setState({
metaData: this.state.metaData.concat({'key': '', 'value': ''})
})
};
render() {
const {formConfig, icons, screenshots, loading, binaryFiles} = this.state;
const {formConfig, icons, screenshots, loading, binaryFiles, metaData} = this.state;
const {getFieldDecorator} = this.props.form;
const {isAppUpdatable} = this.props;
@ -476,6 +491,66 @@ class EditReleaseModal extends React.Component {
/>
)}
</Form.Item>
<Form.Item {...formItemLayout} label="Meta Data">
{getFieldDecorator('meta', {
rules: [{
required: true,
message: 'Please fill empty fields'
}],
initialValue: false
})(
<div>
{
metaData.map((data, index) => {
return (
<InputGroup key={index}>
<Row gutter={8}>
<Col span={10}>
<Input
placeholder="key"
value={data.key}
onChange={(e) => {
metaData[index]['key'] = e.currentTarget.value;
this.setState({
metaData
})
}}/>
</Col>
<Col span={10}>
<Input
placeholder="value"
value={data.value}
onChange={(e) => {
metaData[index].value = e.currentTarget.value;
this.setState({
metaData
});
}}/>
</Col>
<Col span={3}>
<Button type="dashed"
shape="circle"
icon="minus"
onClick={() => {
metaData.splice(index, 1);
this.setState({
metaData
});
}}/>
</Col>
</Row>
</InputGroup>
)
}
)
}
<Button type="dashed" icon="plus" onClick={this.addNewMetaData}>
Add
</Button>
</div>
)}
</Form.Item>
<Divider/>
<Form.Item style={{float: "right", marginLeft: 8}}>

@ -200,7 +200,7 @@ class LifeCycle extends React.Component {
visible={this.state.isReasonModalVisible}
onOk={this.addLifeCycle}
onCancel={this.closeReasonModal}
okText="confirm"
okText="Confirm"
>
<Text>
You are going to change the lifecycle state from,<br/>

@ -69,9 +69,9 @@ class AddNewAppFormComponent extends React.Component {
isError: false
});
const {application} = this.state;
const {price} = application;
const {data, release} = releaseData;
const {formConfig} = this.props;
const {price} = release;
application.subMethod = (price === 0) ? "FREE" : "PAID";
//add release wrapper

@ -320,22 +320,6 @@ class NewAppDetailsForm extends React.Component {
</Select>
)}
</Form.Item>
{/* //todo implement add meta data */}
{/*<Form.Item {...formItemLayout} label="Meta Data">*/}
{/*<InputGroup>*/}
{/*<Row gutter={8}>*/}
{/*<Col span={10}>*/}
{/*<Input placeholder="Key"/>*/}
{/*</Col>*/}
{/*<Col span={12}>*/}
{/*<Input placeholder="value"/>*/}
{/*</Col>*/}
{/*<Col span={2}>*/}
{/*<Button type="dashed" shape="circle" icon="plus"/>*/}
{/*</Col>*/}
{/*</Row>*/}
{/*</InputGroup>*/}
{/*</Form.Item>*/}
<Form.Item style={{float: "right"}}>
<Button type="primary" htmlType="submit">
Next

@ -32,6 +32,7 @@ const formItemLayout = {
};
const {Option} = Select;
const {TextArea} = Input;
const InputGroup = Input.Group;
function getBase64(file) {
return new Promise((resolve, reject) => {
@ -57,7 +58,8 @@ class NewAppUploadForm extends React.Component {
previewImage: '',
binaryFileHelperText: '',
iconHelperText: '',
screenshotHelperText: ''
screenshotHelperText: '',
metaData: []
}
}
@ -88,7 +90,7 @@ class NewAppUploadForm extends React.Component {
description: releaseDescription,
price: (price === undefined) ? 0 : parseInt(price),
isSharedWithAllTenants,
metaData: "string",
metaData: JSON.stringify(this.state.metaData),
releaseType: releaseType
};
@ -182,6 +184,12 @@ class NewAppUploadForm extends React.Component {
});
};
addNewMetaData = () => {
this.setState({
metaData: this.state.metaData.concat({'key': '', 'value': ''})
})
};
render() {
const {formConfig} = this.props;
const {getFieldDecorator} = this.props.form;
@ -194,7 +202,8 @@ class NewAppUploadForm extends React.Component {
previewVisible,
binaryFileHelperText,
iconHelperText,
screenshotHelperText
screenshotHelperText,
metaData
} = this.state;
const uploadButton = (
<div>
@ -390,6 +399,66 @@ class NewAppUploadForm extends React.Component {
/>
)}
</Form.Item>
<Form.Item {...formItemLayout} label="Meta Data">
{getFieldDecorator('meta', {
rules: [{
required: true,
message: 'Please fill empty fields'
}],
initialValue: false
})(
<div>
{
metaData.map((data, index) => {
return (
<InputGroup key={index}>
<Row gutter={8}>
<Col span={5}>
<Input
placeholder="key"
value={data.key}
onChange={(e) => {
metaData[index]['key'] = e.currentTarget.value;
this.setState({
metaData
})
}}/>
</Col>
<Col span={8}>
<Input
placeholder="value"
value={data.value}
onChange={(e) => {
metaData[index].value = e.currentTarget.value;
this.setState({
metaData
});
}}/>
</Col>
<Col span={3}>
<Button type="dashed"
shape="circle"
icon="minus"
onClick={() => {
metaData.splice(index, 1);
this.setState({
metaData
});
}}/>
</Col>
</Row>
</InputGroup>
)
}
)
}
<Button type="dashed" icon="plus" onClick={this.addNewMetaData}>
Add
</Button>
</div>
)}
</Form.Item>
<Form.Item style={{float: "right", marginLeft: 8}}>
<Button type="primary" htmlType="submit">

@ -32,17 +32,13 @@ class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
routes: props.routes
};
const config = this.props.context;
this.Logo = config.theme.logo;
}
//functions for show the drawer
state = {
routes: props.routes,
visible: false,
collapsed: false
};
this.config = this.props.context;
this.Logo = this.config.theme.logo;
}
showDrawer = () => {
this.setState({
@ -67,43 +63,61 @@ class Dashboard extends React.Component {
</div>
<div className="web-layout">
<Menu
theme="light"
mode="horizontal"
defaultSelectedKeys={['1']}
style={{lineHeight: '64px'}}
>
style={{lineHeight: '64px'}}>
<Menu.Item key="1"><Link to="/publisher/apps"><Icon
type="appstore"/>Apps</Link></Menu.Item>
<SubMenu
title={
<span className="submenu-title-wrapper">
<Icon type="plus"/>
Add New App
</span>
}
>
<Menu.Item key="setting:1"><Link to="/publisher/add-new-app/public">Public
APP</Link></Menu.Item>
<Menu.Item key="setting:2"><Link to="/publisher/add-new-app/enterprise">Enterprise
APP</Link></Menu.Item>
<Menu.Item key="setting:3"><Link to="/publisher/add-new-app/web-clip">Web
Clip</Link></Menu.Item>
<Menu.Item key="setting:3"><Link to="/publisher/add-new-app/custom-app">Custom
App</Link></Menu.Item>
}>
<Menu.Item key="setting:1">
<Link to="/publisher/add-new-app/public">Public APP</Link>
</Menu.Item>
<Menu.Item key="setting:2">
<Link to="/publisher/add-new-app/enterprise">Enterprise APP</Link>
</Menu.Item>
<Menu.Item key="setting:3">
<Link to="/publisher/add-new-app/web-clip">Web Clip</Link>
</Menu.Item>
<Menu.Item key="setting:4">
<Link to="/publisher/add-new-app/custom-app">Custom App</Link>
</Menu.Item>
</SubMenu>
<SubMenu
title={
<span className="submenu-title-wrapper">
<Icon type="control"/>Manage
</span>
}>
<Menu.Item key="manage">
<Link to="/publisher/manage">
<Icon type="setting"/> General
</Link>
</Menu.Item>
{this.config.androidEnterpriseToken != null && (
<Menu.Item key="manage-android-enterprise">
<Link to="/publisher/manage/android-enterprise">
<Icon type="android" theme="filled"/> Android Enterprise
</Link>
</Menu.Item>
)}
</SubMenu>
<Menu.Item key="2"><Link to="/publisher/manage"><Icon
type="control"/>Manage</Link></Menu.Item>
<SubMenu className="profile"
title={
<span className="submenu-title-wrapper">
<Icon type="user"/>
Profile
<Icon type="user"/>Profile
</span>
}
>
}>
<Logout/>
</SubMenu>
</Menu>
@ -118,56 +132,63 @@ class Dashboard extends React.Component {
</Button>
</div>
<Drawer
title={<Link to="/publisher/apps"><img alt="logo" src={this.Logo} style={{marginLeft: 30}}
width={"60%"}/></Link>}
title={
<Link to="/publisher/apps">
<img alt="logo"
src={this.Logo}
style={{marginLeft: 30}}
width={"60%"}/>
</Link>
}
placement="left"
closable={false}
onClose={this.onClose}
visible={this.state.visible}
getContainer={false}
style={{position: 'absolute'}}
>
style={{position: 'absolute'}}>
<Menu
theme="light"
mode="inline"
defaultSelectedKeys={['1']}
style={{lineHeight: '64px', width: 231}}
>
style={{lineHeight: '64px', width: 231}}>
<Menu.Item key="1"><Link to="/publisher/apps"><Icon
type="appstore"/>Apps</Link></Menu.Item>
<SubMenu
title={
<span className="submenu-title-wrapper">
<Icon type="plus"/>
Add New App
<Icon type="plus"/>Add New App
</span>
}
>
<Menu.Item key="setting:1"><Link to="/publisher/add-new-app/public">Public
APP</Link></Menu.Item>
<Menu.Item key="setting:2"><Link to="/publisher/add-new-app/enterprise">Enterprise
APP</Link></Menu.Item>
<Menu.Item key="setting:3"><Link to="/publisher/add-new-app/web-clip">Web
Clip</Link></Menu.Item>
<Menu.Item key="setting:4"><Link to="/publisher/add-new-app/custom-app">Custom
App</Link></Menu.Item>
}>
<Menu.Item key="setting:1">
<Link to="/publisher/add-new-app/public">Public APP</Link>
</Menu.Item>
<Menu.Item key="setting:2">
<Link to="/publisher/add-new-app/enterprise">Enterprise APP</Link>
</Menu.Item>
<Menu.Item key="setting:3">
<Link to="/publisher/add-new-app/web-clip">Web Clip</Link>
</Menu.Item>
<Menu.Item key="setting:4">
<Link to="/publisher/add-new-app/custom-app">Custom App</Link>
</Menu.Item>
</SubMenu>
<Menu.Item key="2"><Link to="/publisher/manage"><Icon
type="control"/>Manage</Link></Menu.Item>
<Menu.Item key="2">
<Link to="/publisher/manage">
<Icon type="control"/>Manage
</Link>
</Menu.Item>
</Menu>
</Drawer>
<Menu
mode="horizontal"
defaultSelectedKeys={['1']}
style={{lineHeight: '63px', position: 'fixed', marginLeft: '80%'}}
>
style={{lineHeight: '63px', position: 'fixed', marginLeft: '80%'}}>
<SubMenu
title={
<span className="submenu-title-wrapper">
<Icon type="user"/>
</span>
}
>
}>
<Logout/>
</SubMenu>
</Menu>

@ -18,7 +18,7 @@
import React from "react";
import '../../../../App.css';
import {Typography, Row, Col, message, Card, notification} from "antd";
import {Typography, Row, Col, message, Card, notification, Skeleton} from "antd";
import axios from 'axios';
import ReleaseView from "../../../../components/apps/release/ReleaseView";
import LifeCycle from "../../../../components/apps/release/lifeCycle/LifeCycle";
@ -132,9 +132,9 @@ class Release extends React.Component {
};
render() {
const {app, release, currentLifecycleStatus, lifecycle} = this.state;
const {app, release, currentLifecycleStatus, lifecycle, loading} = this.state;
if (release == null) {
if (release == null && loading === false) {
return (
<div style={{background: '#f0f2f5', padding: 24, minHeight: 780}}>
<Title level={3}>No Apps Found</Title>
@ -149,23 +149,31 @@ class Release extends React.Component {
<Row style={{padding: 10}}>
<Col lg={16} md={24} style={{padding: 3}}>
<Card>
<Skeleton loading={loading} avatar={{size: 'large'}} active paragraph={{rows: 18}}>
{(release !== null) && (
<ReleaseView
app={app}
release={release}
currentLifecycleStatus={currentLifecycleStatus}
lifecycle={lifecycle}
updateRelease={this.updateRelease}
/>
/>)
}
</Skeleton>
</Card>
</Col>
<Col lg={8} md={24} style={{padding: 3}}>
<Card lg={8} md={24}>
<Skeleton loading={loading} active paragraph={{rows: 8}}>
{(release !== null) && (
<LifeCycle
uuid={release.uuid}
currentStatus={release.currentStatus.toUpperCase()}
changeCurrentLifecycleStatus={this.changeCurrentLifecycleStatus}
lifecycle={lifecycle}
/>
/>)
}
</Skeleton>
</Card>
</Col>
</Row>

@ -98,6 +98,14 @@ class ReleaseView extends React.Component {
render() {
const {app,deviceType} = this.props;
const release = app.applicationReleases[0];
let metaData = [];
try{
metaData = JSON.parse(release.metaData);
}catch (e) {
}
return (
<div>
<AppInstallModal
@ -141,6 +149,21 @@ class ReleaseView extends React.Component {
{release.description}
</Paragraph>
<Divider/>
<Text>META DATA</Text>
<Row>
{
metaData.map((data, index)=>{
return (
<Col key={index} lg={8} md={6} xs={24} style={{marginTop:15}}>
<Text>{data.key}</Text><br/>
<Text type="secondary">{data.value}</Text>
</Col>
)
})
}
{(metaData.length===0) && (<Text type="secondary">No meta data available.</Text>)}
</Row>
<Divider/>
<CurrentUsersReview uuid={release.uuid}/>
<Divider dashed={true}/>
<Text>REVIEWS</Text>

Loading…
Cancel
Save