Merge branch 'new-reports-ui' into 'master'

UI for device enrollment reports

See merge request entgra/carbon-device-mgt!415
merge-requests/416/merge
Dharmakeerthi Lasantha 5 years ago
commit 275aa27b5a

@ -8,6 +8,7 @@
"acorn": "^6.2.0",
"antd": "^3.23.5",
"axios": "^0.18.1",
"bizcharts": "^3.5.6",
"bootstrap": "^4.3.1",
"javascript-time-ago": "^2.0.1",
"keymirror": "^0.1.1",
@ -35,6 +36,7 @@
"storm-react-diagrams": "^5.2.1"
},
"devDependencies": {
"@antv/data-set": "^0.10.2",
"@babel/core": "^7.5.4",
"@babel/plugin-proposal-class-properties": "^7.5.0",
"@babel/preset-env": "^7.5.4",

@ -24,7 +24,7 @@ import {
Redirect, Switch,
} from 'react-router-dom';
import axios from "axios";
import {Layout, Spin, Result} from "antd";
import {Layout, Spin, Result, message, notification} from "antd";
import ConfigContext from "./context/ConfigContext";
const {Content} = Layout;
@ -93,6 +93,7 @@ class App extends React.Component {
config: config
});
}
this.getDeviceTypes(config);
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
const redirectUrl = encodeURI(window.location.href);
@ -115,9 +116,30 @@ class App extends React.Component {
});
};
getDeviceTypes = (config) => {
axios.get(
window.location.origin + "/entgra-ui-request-handler/invoke/device-mgt/v1.0/device-types",
).then(res => {
config.deviceTypes = JSON.parse(res.data.data);
this.setState({
config: config,
loading: false
});
}).catch((error) => {
notification["error"]({
message: "There was a problem",
duration: 0,
description:"Error occurred while trying to load device types.",
});
});
};
render() {
const {loading, error} = this.state;
const abc = this.state.deviceTypes;
const applicationView = (
<Router>
<ConfigContext.Provider value={this.state.config}>

@ -0,0 +1,125 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from "react";
import {
PageHeader,
Typography,
Breadcrumb,
Icon,
Tag,
Radio, Select, Button, Card,
Row, Col, message, notification
} from "antd";
import {Link} from "react-router-dom";
import PoliciesTable from "../../../components/Policies/PoliciesTable";
import DevicesTable from "../../../components/Devices/DevicesTable";
import DateRangePicker from "../../../components/Reports/DateRangePicker";
import ReportDeviceTable from "../../../components/Devices/ReportDevicesTable";
import PieChart from "../../../components/Reports/Widgets/PieChart";
import axios from "axios";
import CountWidget from "../../../components/Reports/Widgets/CountWidget";
import {withConfigContext} from "../../../context/ConfigContext";
const {Paragraph} = Typography;
const { CheckableTag } = Tag;
const { Option } = Select;
let config = null;
class DeviceStatusReport extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
config = this.props.context;
const { reportData } = this.props.location;
this.state = {
selectedTags: ['Enrolled'],
paramsObject:{
from:reportData.duration[0],
to:reportData.duration[1]
},
statsObject:{},
statArray:[{item:"ACTIVE",count:0},{item:"INACTIVE",count:0},{item:"REMOVED",count:0}]
};
}
onClickPieChart = (value) => {
console.log(value.data.point.item);
const chartValue = value.data.point.item;
let tempParamObj = this.state.paramsObject;
tempParamObj.status = chartValue;
this.setState({paramsObject:tempParamObj});
console.log(this.state.paramsObject)
};
render() {
const { statArray } = this.state;
const { reportData } = this.props.location;
const params = {...this.state.paramsObject};
return (
<div>
<PageHeader style={{paddingTop: 0}}>
<Breadcrumb style={{paddingBottom: 16}}>
<Breadcrumb.Item>
<Link to="/entgra"><Icon type="home"/> Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Report</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{marginBottom: '10px'}}>
<h3>Summary of enrollments</h3>
<div style={{marginBottom: '10px'}}>
<Select defaultValue="android" style={{ width: 120 , marginRight:10}}>
<Option value="android">Android</Option>
<Option value="ios">IOS</Option>
<Option value="windows">Windows</Option>
</Select>
<Button onClick={this.onSubmitReport} style={{marginLeft:10}} type="primary">Generate Report</Button>
</div>
</div>
<div>
<Card
bordered={true}
hoverable={true}
style={{borderRadius: 5, marginBottom: 10, height:window.innerHeight*0.5}}>
<PieChart onClickPieChart={this.onClickPieChart} reportData={reportData}/>
</Card>
</div>
<div style={{backgroundColor:"#ffffff", borderRadius: 5}}>
<ReportDeviceTable paramsObject={params}/>
</div>
</PageHeader>
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>
</div>
</div>
);
}
}
export default withConfigContext(DeviceStatusReport);

@ -0,0 +1,133 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from "react";
import {
PageHeader,
Typography,
Breadcrumb,
Icon,
Tag,
Radio, Select, Button, Card,
Row, Col, message, notification
} from "antd";
import {Link} from "react-router-dom";
import PoliciesTable from "../../../components/Policies/PoliciesTable";
import DevicesTable from "../../../components/Devices/DevicesTable";
import DateRangePicker from "../../../components/Reports/DateRangePicker";
import ReportDeviceTable from "../../../components/Devices/ReportDevicesTable";
import PieChart from "../../../components/Reports/Widgets/PieChart";
import axios from "axios";
import CountWidget from "../../../components/Reports/Widgets/CountWidget";
import {withConfigContext} from "../../../context/ConfigContext";
const {Paragraph} = Typography;
const { CheckableTag } = Tag;
const { Option } = Select;
let config = null;
class EnrollmentTypeReport extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
config = this.props.context;
const { reportData } = this.props.location;
this.state = {
paramsObject:{
from:reportData.duration[0],
to:reportData.duration[1]
}
};
console.log(reportData.duration);
}
setParam = (tempParamObj, type, value) => {
if(type==="status"){
tempParamObj.status = value;
if(tempParamObj.status) {
delete tempParamObj.status;
}
} else if(type=="ownership"){
tempParamObj.ownership = value;
if(value=="ALL" && tempParamObj.ownership) {
delete tempParamObj.ownership;
}
}
};
onClickPieChart = (value) => {
const chartValue = value.data.point.item;
let tempParamObj = this.state.paramsObject;
console.log(chartValue)
tempParamObj.ownership = chartValue;
this.setState({paramsObject:tempParamObj});
};
render() {
const { reportData } = this.props.location;
const params = {...this.state.paramsObject};
return (
<div>
<PageHeader style={{paddingTop: 0}}>
<Breadcrumb style={{paddingBottom: 16}}>
<Breadcrumb.Item>
<Link to="/entgra"><Icon type="home"/> Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Report</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{marginBottom: '10px'}}>
<h3>Summary of enrollments</h3>
</div>
<div>
<Card
bordered={true}
hoverable={true}
style={{borderRadius: 5, marginBottom: 10, height:window.innerHeight*0.5}}>
<PieChart onClickPieChart={this.onClickPieChart} reportData={reportData}/>
</Card>
</div>
<div style={{backgroundColor:"#ffffff", borderRadius: 5}}>
<ReportDeviceTable paramsObject={params}/>
</div>
</PageHeader>
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>
</div>
</div>
);
}
}
export default withConfigContext(EnrollmentTypeReport);

@ -0,0 +1,172 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from "react";
import {
PageHeader,
Typography,
Breadcrumb,
Icon,
Tag,
Radio, Select, Button, Card,
Row, Col, message, notification
} from "antd";
import {Link, Redirect} from "react-router-dom";
import PoliciesTable from "../../../components/Policies/PoliciesTable";
import DevicesTable from "../../../components/Devices/DevicesTable";
import DateRangePicker from "../../../components/Reports/DateRangePicker";
import ReportDeviceTable from "../../../components/Devices/ReportDevicesTable";
import PieChart from "../../../components/Reports/Widgets/PieChart";
import axios from "axios";
import CountWidget from "../../../components/Reports/Widgets/CountWidget";
import {withConfigContext} from "../../../context/ConfigContext";
const {Paragraph} = Typography;
const { CheckableTag } = Tag;
const { Option } = Select;
let config = null;
class EnrollmentsVsUnenrollmentsReport extends React.Component {
routes;
constructor(props) {
super(props);
this.routes = props.routes;
config = this.props.context;
const { reportData } = this.props.location;
this.state = {
paramsObject:{
from:reportData? reportData.duration[0]: "2019-01-01",
to:reportData? reportData.duration[1]: "2019-01-01"
},
redirect: false
};
this.redirectToHome();
console.log(this.state.paramsObject);
}
setParam = (tempParamObj, type, value) => {
if(type==="status"){
tempParamObj.status = value;
if(tempParamObj.status) {
delete tempParamObj.status;
}
} else if(type=="ownership"){
tempParamObj.ownership = value;
if(value=="ALL" && tempParamObj.ownership) {
delete tempParamObj.ownership;
}
}
};
redirectToHome = () => {
return <Redirect to="/entgra" />
};
setRedirect = (reportData) => {
if(!reportData){
this.setState({
redirect: true
})
}
};
renderRedirect = () => {
if (this.state.redirect) {
return <Redirect to='/entgra' />
}
}
onClickPieChart = (value) => {
const chartValue = value.data.point.item;
let tempParamObj = this.state.paramsObject;
console.log(chartValue)
// tempParamObj.status = chartValue;
if(chartValue==="Enrollments"){
tempParamObj.status = "ACTIVE&status=INACTIVE"
}else{
tempParamObj.status = "REMOVED"
}
this.setState({paramsObject:tempParamObj});
};
render() {
const { reportData } = this.props.location;
console.log("======")
console.log(reportData)
console.log("======")
let reportDataClone = {
params: ["ACTIVE"],
duration: ["2020-01-01","2020-01-01"]
};
const params = {...this.state.paramsObject};
return (
<div>
<div>{!reportData ? (
<Redirect to='/entgra/reports' />
) : (
<PageHeader style={{paddingTop: 0}}>
<Breadcrumb style={{paddingBottom: 16}}>
<Breadcrumb.Item>
<Link to="/entgra"><Icon type="home"/> Home</Link>
</Breadcrumb.Item>
<Breadcrumb.Item>Report</Breadcrumb.Item>
</Breadcrumb>
<div className="wrap" style={{marginBottom: '10px'}}>
<h3>Summary of enrollments</h3>
</div>
<div>
<Card
bordered={true}
hoverable={true}
style={{borderRadius: 5, marginBottom: 10, height:window.innerHeight*0.5}}>
<PieChart onClickPieChart={this.onClickPieChart} reportData={reportData? reportData : reportDataClone}/>
</Card>
</div>
<div style={{backgroundColor:"#ffffff", borderRadius: 5}}>
<ReportDeviceTable paramsObject={params}/>
</div>
</PageHeader>
)}</div>
</div>
);
}
}
export default withConfigContext(EnrollmentsVsUnenrollmentsReport);

@ -0,0 +1,63 @@
import React from "react";
import {Card, Col, Icon} from "antd";
import {Link} from "react-router-dom";
class CountWidget extends React.Component {
constructor(props) {
super(props);
this.routes = props.routes;
this.state = {
statArray:[]
}
}
componentDidMount() {
this.setState({statArray:this.props.statArray})
console.log("$$$$")
console.log(this.props.statArray)
}
render() {
const countObj = [
{item:"All",count:100},
{item:"Enrolled",count:80},
{item:"Unenrolled",count:20}];
const { statArray } = this.state;
let card = statArray.map((data) =>
// <Card
// bordered={true}
// hoverable={true}
// key={data.item}
// style={{borderRadius: 5, marginBottom: 5, width:"100%"}}>
//
// <h3>{data.item} Devices: {data.count}</h3>
//
// </Card>
<Col key={data.item} span={6}>
<Card key={data.item} bordered={true} hoverable={true} style={{borderRadius: 10, marginBottom: 16}}>
<div align='center'>
<h2><b>{data.item}</b></h2>
<h1>{data.count}</h1>
{/*<p>{data.duration}</p>*/}
{/*<ReportFilterModal/>*/}
</div>
</Card>
</Col>
)
return(
<div>
{card}
</div>
)
}
}
export default CountWidget;

@ -0,0 +1,322 @@
import React from "react";
import {
G2,
Chart,
Geom,
Axis,
Tooltip,
Coord,
Label,
Legend,
View,
Guide,
Shape,
Facet,
Util
} from "bizcharts";
import DataSet from "@antv/data-set";
import axios from "axios";
import {message, notification} from "antd";
import {withConfigContext} from "../../../context/ConfigContext";
let config = null;
class PieChart extends React.Component {
constructor(props) {
super(props);
config = this.props.context;
this.state = {
loading:true,
statArray:[]
};
}
componentDidMount() {
let { statArray } = this.state;
const { reportData } = this.props;
let params = {
status: reportData.params[0],
from: reportData.duration[0],
to: reportData.duration[1]
};
const urlSet = {
paramsList:reportData.params,
duration:reportData.duration
};
console.log(urlSet)
if(reportData.params[0]==="Enrollments"){
this.getEnrollmentsVsUnenrollmentsCount(params, urlSet)
}else if(reportData.params[0]==="BYOD"){
this.getEnrollmentTypeCount(params, urlSet);
}else{
this.getCount(params, urlSet);
}
}
clicked = () => {
console.log("Clicked...!!")
};
onChartChange = (data) => {
this.props.onClickPieChart(data);
};
statArray = [];
//Call count APIs and get count for given parameters, then create data object to build pie chart
getCount = (params, urlSet) => {
this.setState({loading: true});
let { statArray } = this.state;
console.log(urlSet);
const urlArray = [];
urlSet.paramsList.map((data) => {
const paramsObj = {
status:data,
from:urlSet.duration[0],
to:urlSet.duration[1]
}
// console.log(paramsObj)
const encodedExtraParams = Object.keys(paramsObj)
.map(key => key + '=' + paramsObj[key]).join('&');
const apiUrl = window.location.origin + config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
"/reports/devices/count?" + encodedExtraParams;
urlArray.push(axios.get(apiUrl, data));
});
console.log(urlArray)
axios.all(urlArray).then(res => {
res.map((response) => {
if(response.status === 200){
let countData = {item:response.config[0], count:parseInt(response.data.data)}
statArray.push(countData);
}
})
this.setState({statArray})
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
//todo display a popup with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification["error"]({
message: "There was a problem",
duration: 0,
description:"Error occurred while trying to get device count.",
});
}
});
};
//Call count APIs and get count for given parameters, then create data object to build pie chart
getEnrollmentsVsUnenrollmentsCount = (params, urlSet) => {
this.setState({loading: true});
let { statArray } = this.state;
console.log(urlSet);
const urlArray = [];
urlSet.paramsList.map((data) => {
const paramsObj = {
from:urlSet.duration[0],
to:urlSet.duration[1]
}
const encodedExtraParams = Object.keys(paramsObj)
.map(key => key + '=' + paramsObj[key]).join('&');
let apiUrl;
if(data==="Enrollments"){
apiUrl = window.location.origin + config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
"/reports/devices/count?status=ACTIVE&status=INACTIVE&" + encodedExtraParams;
}else{
apiUrl = window.location.origin + config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
"/reports/devices/count?status=REMOVED&" + encodedExtraParams;
}
urlArray.push(axios.get(apiUrl, data));
});
console.log(urlArray)
axios.all(urlArray).then(res => {
res.map((response) => {
if(response.status === 200){
let countData = {item:response.config[0], count:parseInt(response.data.data)}
statArray.push(countData);
}
})
this.setState({statArray})
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
//todo display a popup with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification["error"]({
message: "There was a problem",
duration: 0,
description:"Error occurred while trying to get device count.",
});
}
});
};
//Call count APIs and get count for given parameters, then create data object to build pie chart
getEnrollmentTypeCount = (params, urlSet) => {
this.setState({loading: true});
let { statArray } = this.state;
console.log(urlSet);
const urlArray = [];
urlSet.paramsList.map((data) => {
const paramsObj = {
ownership:data,
from:urlSet.duration[0],
to:urlSet.duration[1]
}
const encodedExtraParams = Object.keys(paramsObj)
.map(key => key + '=' + paramsObj[key]).join('&');
const apiUrl = window.location.origin + config.serverConfig.invoker.uri +
config.serverConfig.invoker.deviceMgt +
"/reports/devices/count?" + encodedExtraParams;
urlArray.push(axios.get(apiUrl, data));
});
console.log(urlArray)
axios.all(urlArray).then(res => {
res.map((response) => {
if(response.status === 200){
let countData = {item:response.config[0], count:parseInt(response.data.data)}
statArray.push(countData);
}
})
this.setState({statArray})
}).catch((error) => {
if (error.hasOwnProperty("response") && error.response.status === 401) {
//todo display a popup with error
message.error('You are not logged in');
window.location.href = window.location.origin + '/entgra/login';
} else {
notification["error"]({
message: "There was a problem",
duration: 0,
description:"Error occurred while trying to get device count.",
});
}
});
};
render() {
const { DataView } = DataSet;
const { Html } = Guide;
const { statArray , loading} = this.state;
const dv = new DataView();
dv.source(statArray).transform({
type: "percent",
field: "count",
dimension: "item",
as: "percent"
});
const cols = {
percent: {
formatter: val => {
val = val * 100 + "%";
return val;
}
}
};
return (
<div>
<Chart
height={window.innerHeight/2}
data={dv}
scale={cols}
padding={[20, 25, 20, 20]}
forceFit
onPlotClick={this.onChartChange}
animate={true}
>
<Coord type={"theta"} radius={0.75} innerRadius={0.6} />
<Axis name="percent" />
<Legend
position="right"
offsetY={-window.innerHeight / 2 + 120}
offsetX={-100}
/>
<Tooltip
showTitle={false}
itemTpl="<li><span style=&quot;background-color:{color};&quot; class=&quot;g2-tooltip-marker&quot;></span>{name}: {value}</li>"
/>
<Guide>
<Html
position={["50%", "50%"]}
html="<div style=&quot;color:#8c8c8c;font-size:1.16em;text-align: center;width: 10em;&quot;>Total<br><span style=&quot;color:#262626;font-size:2.5em&quot;>200</span>台</div>"
alignX="middle"
alignY="middle"
/>
</Guide>
<div onClick={this.clicked}>
<Geom
type="intervalStack"
position="percent"
color="item"
tooltip={[
"item*percent",
(item, percent) => {
percent = percent * 100 + "%";
return {
name: item,
value: percent
};
}
]}
style={{
lineWidth: 1,
stroke: "#fff"
}}
>
<Label
content="percent"
formatter={(val, item) => {
return item.point.item + ": " + val;
}}/>
</Geom>
</div>
</Chart>
</div>
);
}
}
export default withConfigContext(PieChart);

@ -34,6 +34,9 @@ import DeviceTypes from "./pages/Dashboard/DeviceTypes/DeviceTypes";
import DeviceEnroll from "./pages/Dashboard/Devices/DeviceEnroll";
import AddNewPolicy from "./pages/Dashboard/Policies/AddNewPolicy";
import Certificates from "./pages/Dashboard/Configurations/Certificates/Certificates";
import ReportDurationItemList from "./pages/Dashboard/Reports/ReportDurationItemList";
import EnrollmentsVsUnenrollmentsReport from "./components/Reports/Templates/EnrollmentsVsUnenrollmentsReport";
import EnrollmentTypeReport from "./components/Reports/Templates/EnrollmentTypeReport";
const routes = [
{
@ -100,6 +103,26 @@ const routes = [
path: '/entgra/certificates',
component: Certificates,
exact: true
},
{
path: '/entgra/reportList',
component: ReportDurationItemList,
exact: true
},
{
path: '/entgra/enrollmentsvsunenrollments',
component: EnrollmentsVsUnenrollmentsReport,
exact: true
},
{
path: '/entgra/enrollmenttype',
component: EnrollmentTypeReport,
exact: true
},
{
path: '/entgra/devicestatus',
component: DeviceStatusReport,
exact: true
}
]
}

@ -0,0 +1,152 @@
/*
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
*
* Entgra (pvt) Ltd. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from "react";
import {
Icon,
Col,
Row, Select,
Radio, Card,
Button
} from "antd";
import {Link} from "react-router-dom";
import moment from "moment";
const { Option } = Select;
class ReportDurationItemList extends React.Component {
constructor(props) {
super(props);
this.state = {
reportParams:["ACTIVE","INACTIVE","REMOVED"],
enrollmentsVsUnenrollmentsParams:["Enrollments", "Unenrollments"],
enrollmentTypeParams:["BYOD", "COPE"]
}
}
durationItemArray = [
{name:"Daily Report", description:"Enrollments of today", duration:[moment().format('YYYY-MM-DD'), moment().add(1, 'days').format('YYYY-MM-DD')]},
{name:"Weekly Report", description:"Enrollments of last 7 days", duration:[moment().subtract(6, 'days').format('YYYY-MM-DD'), moment().add(1, 'days').format('YYYY-MM-DD')]},
{name:"Monthly Report", description:"Enrollments of last month", duration:[moment().subtract(29, 'days').format('YYYY-MM-DD'), moment().add(1, 'days').format('YYYY-MM-DD')]}]
render(){
let itemStatus = this.durationItemArray.map((data) =>
<Col key={data.name} span={6}>
<Link
to={{
//Path to respective report page
pathname: "/entgra/devicestatus",
reportData: {
duration: data.duration,
reportType: data.reportType,
params: this.state.reportParams,
paramsType: data.paramsType
}
}}>
<Card key={data.name} bordered={true} hoverable={true} style={{borderRadius: 10, marginBottom: 16}}>
<div align='center'>
<Icon type="desktop" style={{ fontSize: '25px', color: '#08c' }}/>
<h2><b>{data.name}</b></h2>
<p>{data.description}</p>
{/*<p>{data.duration}</p>*/}
</div>
</Card>
</Link>
</Col>
);
let itemEnrollmentsVsUnenrollments = this.durationItemArray.map((data) =>
<Col key={data.name} span={6}>
<Link
to={{
//Path to respective report page
pathname: "/entgra/enrollmentsvsunenrollments",
reportData: {
duration: data.duration,
reportType: data.reportType,
params: this.state.enrollmentsVsUnenrollmentsParams,
paramsType: data.paramsType
}
}}>
<Card key={data.name} bordered={true} hoverable={true} style={{borderRadius: 10, marginBottom: 16}}>
<div align='center'>
<Icon type="desktop" style={{ fontSize: '25px', color: '#08c' }}/>
<h2><b>{data.name}</b></h2>
<p>{data.description}</p>
</div>
</Card>
</Link>
</Col>
);
let itemEnrollmentType = this.durationItemArray.map((data) =>
<Col key={data.name} span={6}>
<Link
to={{
//Path to respective report page
pathname: "/entgra/enrollmenttype",
reportData: {
duration: data.duration,
reportType: data.reportType,
params: this.state.enrollmentTypeParams,
paramsType: data.paramsType
}
}}>
<Card key={data.name} bordered={true} hoverable={true} style={{borderRadius: 10, marginBottom: 16}}>
<div align='center'>
<Icon type="desktop" style={{ fontSize: '25px', color: '#08c' }}/>
<h2><b>{data.name}</b></h2>
<p>{data.description}</p>
</div>
</Card>
</Link>
</Col>
);
return(
<div>
<div style={{borderRadius: 5}}>
<Row gutter={16} >
{itemStatus}
</Row>
</div>
<div style={{borderRadius: 5}}>
<Row gutter={16} >
{itemEnrollmentsVsUnenrollments}
</Row>
</div>
<div style={{borderRadius: 5}}>
<Row gutter={16} >
{itemEnrollmentType}
</Row>
</div>
</div>
)
}
}
export default ReportDurationItemList;

@ -24,9 +24,7 @@ import {
Icon
} from "antd";
import {Link} from "react-router-dom";
import ReportDeviceTable from "../../../components/Devices/ReportDevicesTable";
import Filter from "../../../components/Reports/Filter";
import DateRangePicker from "../../../components/Reports/DateRangePicker";
import ReportDurationItemList from "./ReportDurationItemList";
const {Paragraph} = Typography;
@ -37,17 +35,16 @@ class Reports extends React.Component {
super(props);
this.routes = props.routes;
this.state = {
paramsObject:{}
paramsObject:{},
}
}
//Get modified value from datepicker and set it to paramsObject
//Get modified value from datepicker and set it to paramsObject
updateDurationValue = (modifiedFromDate,modifiedToDate) => {
let tempParamObj = this.state.paramsObject;
tempParamObj.from = modifiedFromDate;
tempParamObj.to = modifiedToDate;
this.setState({paramsObject:tempParamObj});
}
};
//Get modified value from filters and set it to paramsObject
updateFiltersValue = (modifiedValue,filterType) => {
@ -64,13 +61,13 @@ class Reports extends React.Component {
}
}
this.setState({paramsObject:tempParamObj});
}
};
render() {
//Arrays for filters
const statusObj = ['ALL','ACTIVE','INACTIVE','REMOVED'];
const ownershipObj = ['ALL','BYOD','COPE'];
const params = {...this.state.paramsObject};
return (
@ -84,41 +81,7 @@ class Reports extends React.Component {
</Breadcrumb>
<div className="wrap">
<h3>Reports</h3>
<Paragraph>
To generate a report, select a duration and apply filters
</Paragraph>
<div style={{paddingBottom:'5px'}}>
<table>
<tbody>
<tr style={{fontSize:'12px'}}>
<td>Select Duration</td>
<td>Device Status</td>
<td>Device Ownership</td>
</tr>
<tr>
<td>
<DateRangePicker
updateDurationValue={this.updateDurationValue}/>
</td>
<td>
<Filter
updateFiltersValue={this.updateFiltersValue}
dropDownItems={statusObj}
dropDownName={"Device Status"}/>
</td>
<td>
<Filter
updateFiltersValue={this.updateFiltersValue}
dropDownItems={ownershipObj}
dropDownName={"Device Ownership"}/>
</td>
</tr>
</tbody>
</table>
</div>
<div style={{backgroundColor:"#ffffff", borderRadius: 5}}>
<ReportDeviceTable paramsObject={params}/>
</div>
<ReportDurationItemList/>
</div>
</PageHeader>
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>

@ -42,6 +42,7 @@ import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
@SwaggerDefinition(
info = @Info(
@ -127,7 +128,7 @@ public interface ReportManagementService {
@ApiParam(
name = "status",
value = "Provide the device status details, such as active or inactive.")
@QueryParam("status") String status,
@QueryParam("status") List<String> status,
@ApiParam(
name = "ownership",
allowableValues = "BYOD, COPE",
@ -157,4 +158,76 @@ public interface ReportManagementService {
defaultValue = "5")
@QueryParam("limit")
int limit) throws ReportManagementException;
@GET
@Path("/devices/count")
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",
value = "Getting Details of Registered Devices",
notes = "Provides details of all the devices enrolled with WSO2 IoT Server.",
tags = "Device Management",
extensions = {
@Extension(properties = {
@ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:view")
})
}
)
@ApiResponses(
value = {
@ApiResponse(
code = 200,
message = "OK. \n Successfully fetched the list of devices.",
response = DeviceList.class,
responseHeaders = {
@ResponseHeader(
name = "Content-Type",
description = "The content type of the body"),
@ResponseHeader(
name = "ETag",
description = "Entity Tag of the response resource.\n" +
"Used by caches, or in conditional requests."),
@ResponseHeader(
name = "Last-Modified",
description = "Date and time the resource was last modified.\n" +
"Used by caches, or in conditional requests."),
}),
@ApiResponse(
code = 400,
message = "Bad Request. \n Invalid device status type received. \n" +
"Valid status types are NEW | CHECKED",
response = ErrorResponse.class),
@ApiResponse(
code = 404,
message = "Not Found. \n There are no devices.",
response = ErrorResponse.class),
@ApiResponse(
code = 500,
message = "Internal Server Error. " +
"\n Server error occurred while fetching the device list.",
response = ErrorResponse.class)
})
Response getDevicesByDurationCount(
@ApiParam(
name = "status",
value = "Provide the device status details, such as active or inactive.")
@QueryParam("status") List<String> status,
@ApiParam(
name = "ownership",
allowableValues = "BYOD, COPE",
value = "Provide the ownership status of the device. The following values can be assigned:\n" +
"- BYOD: Bring Your Own Device\n" +
"- COPE: Corporate-Owned, Personally-Enabled")
@QueryParam("ownership") String ownership,
@ApiParam(
name = "fromDate",
value = "Start date of the duration",
required = true)
@QueryParam("from") String fromDate,
@ApiParam(
name = "toDate",
value = "end date of the duration",
required = true)
@QueryParam("to") String toDate) throws ReportManagementException;
}

@ -23,6 +23,7 @@ import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.PaginationResult;
import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException;
import org.wso2.carbon.device.mgt.common.exceptions.ReportManagementException;
import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceList;
import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse;
@ -54,13 +55,13 @@ public class ReportManagementServiceImpl implements ReportManagementService {
@Path("/devices")
@Override
public Response getDevicesByDuration(
@QueryParam("status") String status,
@QueryParam("status") List<String> status,
@QueryParam("ownership") String ownership,
@QueryParam("from") String fromDate,
@QueryParam("to") String toDate,
@DefaultValue("0")
@QueryParam("offset") int offset,
@DefaultValue("5")
@DefaultValue("10")
@QueryParam("limit") int limit) {
try {
RequestValidationUtil.validatePaginationParameters(offset, limit);
@ -68,15 +69,12 @@ public class ReportManagementServiceImpl implements ReportManagementService {
PaginationResult result;
DeviceList devices = new DeviceList();
if (!StringUtils.isBlank(status)) {
request.setStatus(status);
}
if (!StringUtils.isBlank(ownership)) {
request.setOwnership(ownership);
}
result = DeviceMgtAPIUtils.getReportManagementService()
.getDevicesByDuration(request, fromDate, toDate);
.getDevicesByDuration(request, status, fromDate, toDate);
if (result.getData().isEmpty()) {
String msg = "No devices have enrolled between " + fromDate + " to " + toDate +
" or doesn't match with" +
@ -94,4 +92,24 @@ public class ReportManagementServiceImpl implements ReportManagementService {
new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build();
}
}
@GET
@Path("/devices/count")
@Override
public Response getDevicesByDurationCount(
@QueryParam("status") List<String> status,
@QueryParam("ownership") String ownership,
@QueryParam("from") String fromDate,
@QueryParam("to") String toDate) {
int deviceCount;
try {
deviceCount = DeviceMgtAPIUtils.getReportManagementService().getDevicesByDurationCount(status, ownership, fromDate, toDate);
return Response.status(Response.Status.OK).entity(deviceCount).build();
} catch (ReportManagementException e) {
String errorMessage = "Error while retrieving device count.";
log.error(errorMessage, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(
new ErrorResponse.ErrorResponseBuilder().setMessage(errorMessage).build()).build();
}
}
}

@ -21,6 +21,8 @@ import org.wso2.carbon.device.mgt.common.PaginationRequest;
import org.wso2.carbon.device.mgt.common.PaginationResult;
import org.wso2.carbon.device.mgt.common.exceptions.ReportManagementException;
import java.util.List;
/**
* This is the service class for reports which connects with DAO layer
*/
@ -36,6 +38,9 @@ public interface ReportManagementService {
* @throws {@Link DeviceManagementException} When error occurred while validating device list page size
* @throws {@Link ReportManagementException} When failed to retrieve devices.
*/
PaginationResult getDevicesByDuration(PaginationRequest request, String fromDate, String toDate)
PaginationResult getDevicesByDuration(PaginationRequest request, List<String> statusList, String fromDate, String toDate)
throws ReportManagementException;
int getDevicesByDurationCount(List<String> statusList, String ownership, String fromDate, String toDate)
throws ReportManagementException;
}

@ -552,10 +552,15 @@ public interface DeviceDAO {
*
*/
List<Device> getDevicesByDuration(PaginationRequest request,
List<String> statusList,
int tenantId,
String fromDate,
String toDate) throws DeviceManagementDAOException;
int getDevicesByDurationCount(
List<String> statusList, String ownership, String fromDate, String toDate, int tenantId)
throws DeviceManagementDAOException;
/**
* Retrieve device location information
* @param deviceIdentifier Device Identifier object

@ -455,12 +455,12 @@ public class GenericDeviceDAOImpl extends AbstractDeviceDAOImpl {
}
@Override
public List<Device> getDevicesByDuration(PaginationRequest request, int tenantId,
public List<Device> getDevicesByDuration(PaginationRequest request, List<String> statusList, int tenantId,
String fromDate, String toDate)
throws DeviceManagementDAOException {
List<Device> devices;
String deviceStatus = request.getStatus();
String ownership = request.getOwnership();
boolean isStatusProvided = false;
String sql = "SELECT " +
"d.ID AS DEVICE_ID, " +
@ -479,9 +479,15 @@ public class GenericDeviceDAOImpl extends AbstractDeviceDAOImpl {
"e.TENANT_ID = ? AND " +
"e.DATE_OF_ENROLMENT BETWEEN ? AND ?";
if (deviceStatus != null) {
sql = sql + " AND e.STATUS = ?";
//Add the query for status
StringBuilder sqlBuilder = new StringBuilder(sql);
isStatusProvided = buildStatusQuery(statusList, sqlBuilder);
sql = sqlBuilder.toString();
if(statusList != null && !statusList.isEmpty()){
isStatusProvided = true;
}
if (ownership != null) {
sql = sql + " AND e.OWNERSHIP = ?";
}
@ -494,8 +500,10 @@ public class GenericDeviceDAOImpl extends AbstractDeviceDAOImpl {
stmt.setInt(paramIdx++, tenantId);
stmt.setString(paramIdx++, fromDate);
stmt.setString(paramIdx++, toDate);
if (deviceStatus != null) {
stmt.setString(paramIdx++, deviceStatus);
if (isStatusProvided) {
for (String status : statusList) {
stmt.setString(paramIdx++, status);
}
}
if (ownership != null) {
stmt.setString(paramIdx++, ownership);
@ -518,6 +526,73 @@ public class GenericDeviceDAOImpl extends AbstractDeviceDAOImpl {
return devices;
}
@Override
public int getDevicesByDurationCount(List<String> statusList, String ownership, String fromDate, String toDate, int tenantId) throws DeviceManagementDAOException {
int deviceCount = 0;
boolean isStatusProvided = false;
String sql = "SELECT " +
"COUNT(d.ID) AS DEVICE_COUNT " +
"FROM DM_DEVICE AS d , DM_ENROLMENT AS e , DM_DEVICE_TYPE AS t " +
"WHERE d.ID = e.DEVICE_ID AND " +
"d.DEVICE_TYPE_ID = t.ID AND " +
"e.TENANT_ID = ? AND " +
"e.DATE_OF_ENROLMENT BETWEEN ? AND ?";
//Add the query for status
StringBuilder sqlBuilder = new StringBuilder(sql);
isStatusProvided = buildStatusQuery(statusList, sqlBuilder);
sql = sqlBuilder.toString();
if (ownership != null) {
sql = sql + " AND e.OWNERSHIP = ?";
}
try (Connection conn = this.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
int paramIdx = 1;
stmt.setInt(paramIdx++, tenantId);
stmt.setString(paramIdx++, fromDate);
stmt.setString(paramIdx++, toDate);
if (isStatusProvided) {
for (String status : statusList) {
stmt.setString(paramIdx++, status);
}
}
if (ownership != null) {
stmt.setString(paramIdx++, ownership);
}
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
deviceCount = rs.getInt("DEVICE_COUNT");
}
}
} catch (SQLException e) {
String msg = "Error occurred while retrieving information of all " +
"registered devices under tenant id " + tenantId;
log.error(msg, e);
throw new DeviceManagementDAOException(msg, e);
}
return deviceCount;
}
protected boolean buildStatusQuery(List<String> statusList, StringBuilder sqlBuilder) {
if (statusList != null && !statusList.isEmpty() && !statusList.get(0).isEmpty()) {
sqlBuilder.append(" AND e.STATUS IN(");
for (int i = 0; i < statusList.size(); i++) {
sqlBuilder.append("?");
if (i != statusList.size() - 1) {
sqlBuilder.append(",");
}
}
sqlBuilder.append(")");
return true;
}else {
return false;
}
}
/**
* Get the list of devices that matches with the given device name and (or) device type.
*

@ -459,7 +459,7 @@ public class OracleDeviceDAOImpl extends AbstractDeviceDAOImpl {
}
@Override
public List<Device> getDevicesByDuration(PaginationRequest request, int tenantId,
public List<Device> getDevicesByDuration(PaginationRequest request, List<String> statusList, int tenantId,
String fromDate, String toDate)
throws DeviceManagementDAOException {
List<Device> devices;
@ -522,6 +522,11 @@ public class OracleDeviceDAOImpl extends AbstractDeviceDAOImpl {
return devices;
}
@Override
public int getDevicesByDurationCount(List<String> statusList, String ownership, String fromDate, String toDate, int tenantId) throws DeviceManagementDAOException {
return 0;
}
/**
* Get the list of devices that matches with the given device name and (or) device type.
*

@ -438,7 +438,7 @@ public class PostgreSQLDeviceDAOImpl extends AbstractDeviceDAOImpl {
}
@Override
public List<Device> getDevicesByDuration(PaginationRequest request, int tenantId,
public List<Device> getDevicesByDuration(PaginationRequest request, List<String> statusList, int tenantId,
String fromDate, String toDate)
throws DeviceManagementDAOException {
List<Device> devices;
@ -501,6 +501,11 @@ public class PostgreSQLDeviceDAOImpl extends AbstractDeviceDAOImpl {
return devices;
}
@Override
public int getDevicesByDurationCount(List<String> statusList, String ownership, String fromDate, String toDate, int tenantId) throws DeviceManagementDAOException {
return 0;
}
/**
* Get the list of devices that matches with the given device name and (or) device type.
*

@ -601,7 +601,7 @@ public class SQLServerDeviceDAOImpl extends AbstractDeviceDAOImpl {
}
@Override
public List<Device> getDevicesByDuration(PaginationRequest request, int tenantId,
public List<Device> getDevicesByDuration(PaginationRequest request, List<String> statusList, int tenantId,
String fromDate, String toDate)
throws DeviceManagementDAOException {
List<Device> devices;
@ -664,6 +664,11 @@ public class SQLServerDeviceDAOImpl extends AbstractDeviceDAOImpl {
return devices;
}
@Override
public int getDevicesByDurationCount(List<String> statusList, String ownership, String fromDate, String toDate, int tenantId) throws DeviceManagementDAOException {
return 0;
}
@Override
public int getSubscribedDeviceCount(List<Integer> deviceIds, int tenantId, String status)
throws DeviceManagementDAOException {

@ -49,7 +49,7 @@ public class ReportManagementServiceImpl implements ReportManagementService {
}
@Override
public PaginationResult getDevicesByDuration(PaginationRequest request, String fromDate,
public PaginationResult getDevicesByDuration(PaginationRequest request, List<String> statusList, String fromDate,
String toDate)
throws ReportManagementException {
PaginationResult paginationResult = new PaginationResult();
@ -64,6 +64,7 @@ public class ReportManagementServiceImpl implements ReportManagementService {
DeviceManagementDAOFactory.openConnection();
List<Device> devices = deviceDAO.getDevicesByDuration(
request,
statusList,
DeviceManagementDAOUtil.getTenantId(),
fromDate,
toDate
@ -85,4 +86,23 @@ public class ReportManagementServiceImpl implements ReportManagementService {
DeviceManagementDAOFactory.closeConnection();
}
}
@Override
public int getDevicesByDurationCount(List<String> statusList, String ownership, String fromDate, String toDate)
throws ReportManagementException {
try {
DeviceManagementDAOFactory.openConnection();
return deviceDAO.getDevicesByDurationCount(statusList, ownership, fromDate, toDate, DeviceManagementDAOUtil.getTenantId());
} catch (DeviceManagementDAOException e) {
String msg = "Error occurred in while retrieving device count by status for " + statusList + "devices.";
log.error(msg, e);
throw new ReportManagementException(msg, e);
} catch (SQLException e) {
String msg = "Error occurred while opening a connection to the data source";
log.error(msg, e);
throw new ReportManagementException(msg, e);
} finally {
DeviceManagementDAOFactory.closeConnection();
}
}
}

Loading…
Cancel
Save