diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/AppNotInstalledDevicesReport.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/AppNotInstalledDevicesReport.js new file mode 100644 index 00000000000..bc52a620417 --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/AppNotInstalledDevicesReport.js @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2020, 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, Breadcrumb, Icon, Button } from 'antd'; + +import { Link } from 'react-router-dom'; +import { withConfigContext } from '../../../context/ConfigContext'; + +import AppListDropDown from '../Widgets/AppListDropDown'; +import ReportDevicesTable from '../Widgets/ReportDevicesTable'; +import AppVersionDropDown from '../Widgets/AppVersionDropDown'; + +// eslint-disable-next-line no-unused-vars +let config = null; +let url; +let packageName; +let version; + +class AppNotInstalledDevicesReport extends React.Component { + routes; + + constructor(props) { + super(props); + this.routes = props.routes; + config = this.props.context; + this.state = { + apiUrl: null, + visible: false, + packageName: null, + version: 'all', + }; + } + + getAppList = appPackageName => { + packageName = appPackageName; + this.setState({ packageName }); + }; + + getVersion = appVersion => { + version = appVersion; + this.setState({ version }); + }; + + onClickGenerateButton = () => { + const { packageName, version } = this.state; + if (version === 'all') { + url = + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.deviceMgt + + '/reports/devices/android/' + + packageName + + '/not-installed?'; + } else { + url = + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.deviceMgt + + '/reports/devices/android/' + + packageName + + '/not-installed?app-version=' + + version + + '&'; + } + this.setState({ apiUrl: url }); + }; + + render() { + const { apiUrl, packageName } = this.state; + return ( +
+ + + + + Home + + + + Reports + + App NOT Installed Devices Report + +
+

Policy Report

+ +
+
+ +
+ +
+ +
+ + +
+
+ +
+
+
+
+
+ ); + } +} + +export default withConfigContext(AppNotInstalledDevicesReport); diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/DeviceStatusReport.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/DeviceStatusReport.js index 5e158350783..295a6ff773e 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/DeviceStatusReport.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/DeviceStatusReport.js @@ -188,7 +188,10 @@ class DeviceStatusReport extends React.Component { Home - Report + + Reports + + Device Status Report

Device Status Report

diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/EnrollmentTypeReport.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/EnrollmentTypeReport.js index 3a936a26ae9..4e98335d8e5 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/EnrollmentTypeReport.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/EnrollmentTypeReport.js @@ -177,7 +177,10 @@ class EnrollmentTypeReport extends React.Component { Home - Report + + Reports + + Device Enrollment Type Report

Device Enrollment Type Report

diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/EnrollmentsVsUnenrollmentsReport.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/EnrollmentsVsUnenrollmentsReport.js index 983c6fc0217..f19b36126e1 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/EnrollmentsVsUnenrollmentsReport.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/EnrollmentsVsUnenrollmentsReport.js @@ -177,7 +177,12 @@ class EnrollmentsVsUnenrollmentsReport extends React.Component { Home - Report + + Reports + + + Enrollments vs Unenrollments Report +

Enrollments vs Unenrollments Report

diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/PolicyReport.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/PolicyReport.js index 2e3e859308f..59143a5c5be 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/PolicyReport.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Templates/PolicyReport.js @@ -132,7 +132,10 @@ class PolicyReport extends React.Component { Home - Report + + Reports + + Policy Compliance Report

Policy Report

diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/AppListDropDown.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/AppListDropDown.js new file mode 100644 index 00000000000..d12cb3c0798 --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/AppListDropDown.js @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2020, 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 { message, notification, Select } from 'antd'; +import axios from 'axios'; +import { withConfigContext } from '../../../context/ConfigContext'; + +const { Option } = Select; + +class AppListDropDown extends React.Component { + constructor(props) { + super(props); + this.state = { + selectItem: [], + }; + } + + componentDidMount() { + this.fetchFullAppList(); + } + + fetchFullAppList = () => { + const config = this.props.context; + axios + .get( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.deviceMgt + + '/devices/android/applications?offset=0&limit=-1', + ) + .then(res => { + if (res.status === 200) { + let selectItem; + selectItem = res.data.data.applicationList.map(data => ( + + )); + this.setState({ selectItem }); + } + }) + .catch(error => { + if (error.hasOwnProperty('response') && error.response.status === 401) { + // todo display a popop 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 load application list.', + }); + } + }); + }; + + onChange = (value, data) => { + this.props.getAppList(data.key); + }; + + render() { + const { selectItem } = this.state; + return ( +
+ +
+ ); + } +} + +export default withConfigContext(AppListDropDown); diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/AppVersionDropDown.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/AppVersionDropDown.js new file mode 100644 index 00000000000..40e31a0fbe7 --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/AppVersionDropDown.js @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020, 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 { message, notification, Select } from 'antd'; +import axios from 'axios'; +import { withConfigContext } from '../../../context/ConfigContext'; + +const { Option } = Select; + +class AppVersionDropDown extends React.Component { + constructor(props) { + super(props); + this.state = { + selectItem: [], + loading: false, + }; + } + + componentDidMount() { + if (this.props.packageName) { + this.fetchVersionList(); + } + } + + // Rerender component when parameters change + componentDidUpdate(prevProps, prevState, snapshot) { + if (prevProps.packageName !== this.props.packageName) { + this.fetchVersionList(); + } + } + + fetchVersionList = () => { + const config = this.props.context; + this.setState({ loading: true }); + axios + .get( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.deviceMgt + + '/devices/application/' + + this.props.packageName + + '/versions', + ) + .then(res => { + if (res.status === 200) { + let selectItem; + selectItem = JSON.parse(res.data.data).map(data => ( + + )); + this.setState({ selectItem, loading: false }); + } + }) + .catch(error => { + if (error.hasOwnProperty('response') && error.response.status === 401) { + // todo display a popop 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 load application list.', + }); + } + }); + }; + + onChange = value => { + this.props.getVersion(value); + }; + + render() { + const { selectItem, loading } = this.state; + return ( +
+ +
+ ); + } +} + +export default withConfigContext(AppVersionDropDown); diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/ReportDevicesTable.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/ReportDevicesTable.js new file mode 100644 index 00000000000..5710a79882d --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/components/Reports/Widgets/ReportDevicesTable.js @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2020, 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 axios from 'axios'; +import { Icon, message, notification, Table, Tag, Tooltip } from 'antd'; +import TimeAgo from 'javascript-time-ago'; +// Load locale-specific relative date/time formatting rules. +import en from 'javascript-time-ago/locale/en'; +import { withConfigContext } from '../../../context/ConfigContext'; + +let config = null; + +const columns = [ + { + title: 'Device', + dataIndex: 'name', + width: 100, + }, + { + title: 'Type', + dataIndex: 'type', + key: 'type', + // eslint-disable-next-line react/display-name + render: type => { + const defaultPlatformIcons = config.defaultPlatformIcons; + let icon = defaultPlatformIcons.default.icon; + let color = defaultPlatformIcons.default.color; + let theme = defaultPlatformIcons.default.theme; + + if (defaultPlatformIcons.hasOwnProperty(type)) { + icon = defaultPlatformIcons[type].icon; + color = defaultPlatformIcons[type].color; + theme = defaultPlatformIcons[type].theme; + } + + return ( + + + + ); + }, + // todo add filtering options + }, + { + title: 'Owner', + dataIndex: 'enrolmentInfo', + key: 'owner', + render: enrolmentInfo => enrolmentInfo.owner, + // todo add filtering options + }, + { + title: 'Ownership', + dataIndex: 'enrolmentInfo', + key: 'ownership', + render: enrolmentInfo => enrolmentInfo.ownership, + // todo add filtering options + }, + { + title: 'Status', + dataIndex: 'enrolmentInfo', + key: 'status', + // eslint-disable-next-line react/display-name + render: enrolmentInfo => { + const status = enrolmentInfo.status.toLowerCase(); + let color = '#f9ca24'; + switch (status) { + case 'active': + color = '#badc58'; + break; + case 'created': + color = '#6ab04c'; + break; + case 'removed': + color = '#ff7979'; + break; + case 'inactive': + color = '#f9ca24'; + break; + case 'blocked': + color = '#636e72'; + break; + } + return {status}; + }, + // todo add filtering options + }, + { + title: 'Last Updated', + dataIndex: 'enrolmentInfo', + key: 'dateOfLastUpdate', + // eslint-disable-next-line react/display-name + render: data => { + const { dateOfLastUpdate } = data; + const timeAgoString = getTimeAgo(dateOfLastUpdate); + return ( + + {timeAgoString} + + ); + }, + // todo add filtering options + }, +]; + +const getTimeAgo = time => { + const timeAgo = new TimeAgo('en-US'); + return timeAgo.format(time); +}; + +class ReportDeviceTable extends React.Component { + constructor(props) { + super(props); + config = this.props.context; + TimeAgo.addLocale(en); + this.state = { + data: [], + pagination: {}, + loading: false, + selectedRows: [], + paramsObj: {}, + }; + } + + rowSelection = { + onChange: (selectedRowKeys, selectedRows) => { + this.setState({ + selectedRows: selectedRows, + }); + }, + }; + + componentDidMount() { + if (this.props.apiUrl) { + this.fetch(); + } + } + + // Rerender component when parameters change + componentDidUpdate(prevProps, prevState, snapshot) { + if (prevProps.apiUrl !== this.props.apiUrl) { + this.fetch(); + } + } + + // fetch data from api + fetch = (params = {}) => { + this.setState({ loading: true }); + // get current page + const currentPage = params.hasOwnProperty('page') ? params.page : 1; + + const extraParams = { + offset: 10 * (currentPage - 1), // calculate the offset + limit: 10, + }; + + const encodedExtraParams = Object.keys(extraParams) + .map(key => key + '=' + extraParams[key]) + .join('&'); + + // send request to the invokerss + axios + .get(this.props.apiUrl + encodedExtraParams) + .then(res => { + if (res.status === 200) { + const pagination = { ...this.state.pagination }; + this.setState({ + loading: false, + data: res.data.data, + pagination, + }); + } + }) + .catch(error => { + if (error.hasOwnProperty('response') && error.response.status === 401) { + // todo display a popop 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 load devices.', + }); + } + + this.setState({ loading: false }); + }); + }; + + handleTableChange = (pagination, filters, sorter) => { + const pager = { ...this.state.pagination }; + pager.current = pagination.current; + this.setState({ + pagination: pager, + }); + this.fetch({ + results: pagination.pageSize, + page: pagination.current, + sortField: sorter.field, + sortOrder: sorter.order, + ...filters, + }); + }; + + render() { + const { data, pagination, loading } = this.state; + return ( +
+ + record.deviceIdentifier + + record.enrolmentInfo.owner + + record.enrolmentInfo.ownership + } + dataSource={data.devices} + pagination={{ + ...pagination, + size: 'small', + total: data.count, + showTotal: (total, range) => + `showing ${range[0]}-${range[1]} of ${total} devices`, + }} + loading={loading} + onChange={this.handleTableChange} + rowSelection={this.rowSelection} + /> + + ); + } +} + +export default withConfigContext(ReportDeviceTable); diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js index c081b3776cc..acbd25f60e6 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/index.js @@ -31,6 +31,7 @@ import PolicyReport from './components/Reports/Templates/PolicyReport'; import DeviceStatusReport from './components/Reports/Templates/DeviceStatusReport'; import PolicyReportHome from './pages/Dashboard/Reports/PolicyReportHome'; import ReportDurationItemList from './pages/Dashboard/Reports/ReportDurationItemList'; +import AppNotInstalledDevicesReport from './components/Reports/Templates/AppNotInstalledDevicesReport'; const routes = [ { @@ -128,6 +129,11 @@ const routes = [ component: DeviceStatusReport, exact: true, }, + { + path: '/entgra/reports/app-not-installed', + component: AppNotInstalledDevicesReport, + exact: true, + }, ], }, ]; diff --git a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/PolicyReportHome.js b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/PolicyReportHome.js index 9115112daca..10fbb613142 100644 --- a/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/PolicyReportHome.js +++ b/components/device-mgt/io.entgra.device.mgt.ui/react-app/src/pages/Dashboard/Reports/PolicyReportHome.js @@ -146,6 +146,35 @@ class PolicyReportHome extends React.Component { + + + + +
+ +

+ App NOT Installed Devices Report +

+

Report for all device types

+
+
+ + diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/ApplicationList.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/ApplicationList.java new file mode 100644 index 00000000000..2fe67e10bfa --- /dev/null +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/beans/ApplicationList.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020, 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. + */ + +package org.wso2.carbon.device.mgt.jaxrs.beans; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModelProperty; +import org.wso2.carbon.device.mgt.common.app.mgt.Application; + +import java.util.ArrayList; +import java.util.List; + +public class ApplicationList extends BasePaginatedResult{ + + private List applicationList = new ArrayList<>(); + + @ApiModelProperty(value = "List of applications returned") + @JsonProperty("applications") + public List getList() { + return applicationList; + } + + public void setList(List applicationList) { + this.applicationList = applicationList; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("{\n"); + sb.append(" count: ").append(getCount()).append(",\n"); + sb.append(" applications: [").append(applicationList).append("\n"); + sb.append("]}\n"); + return sb.toString(); + } +} diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceManagementService.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceManagementService.java index be4cf349d23..a7b96b501a4 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceManagementService.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/DeviceManagementService.java @@ -60,6 +60,7 @@ import org.wso2.carbon.device.mgt.common.policy.mgt.Policy; import org.wso2.carbon.device.mgt.common.policy.mgt.monitor.NonComplianceData; import org.wso2.carbon.device.mgt.common.search.PropertyMap; import org.wso2.carbon.device.mgt.common.search.SearchContext; +import org.wso2.carbon.device.mgt.jaxrs.beans.ApplicationList; import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceList; import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse; import org.wso2.carbon.device.mgt.jaxrs.beans.OperationRequest; @@ -80,7 +81,6 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.List; -import java.util.Map; /** * Device related REST-API. This can be used to manipulated device related details. @@ -2028,4 +2028,111 @@ public interface DeviceManagementService { required = true) @PathParam("id") int id); + + @GET + @Path("/{device-type}/applications") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "GET", + value = "Getting Details of Applications", + notes = "Provides details of installed applications in all the devices enrolled with Entgra IoT Server.", + tags = "Device Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:applications") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully fetched the list of applications.", + response = ApplicationList.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 = 404, + message = "Not Found. \n There are no applications.", + response = ErrorResponse.class), + @ApiResponse( + code = 500, + message = "Error occurred while getting the application data.", + response = ErrorResponse.class) + }) + Response getApplications( + @ApiParam( + name = "device-type", + value = "Device type (platform) of the application", + required = true) + @PathParam("device-type") + String deviceType, + @ApiParam( + name = "offset", + value = "The starting pagination index for the complete list of qualified items.", + defaultValue = "0") + @QueryParam("offset") + int offset, + @ApiParam( + name = "limit", + value = "Provide how many device details you require from the starting pagination index/offset.", + defaultValue = "10") + @QueryParam("limit") + int limit); + + @GET + @Path("/application/{package-name}/versions") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "GET", + value = "Getting versions of a given application", + notes = "Provides versions of a given application installed in devices of Entgra IoT Server.", + tags = "Device Management", + extensions = { + @Extension(properties = { + @ExtensionProperty(name = Constants.SCOPE, value = "perm:devices:applications") + }) + } + ) + @ApiResponses( + value = { + @ApiResponse( + code = 200, + message = "OK. \n Successfully fetched the list of app versions.", + 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 = 500, + message = "Error occurred while getting the version data.", + response = ErrorResponse.class) + }) + Response getAppVersions( + @ApiParam( + name = "package-name", + value = "The package name of the app.", + required = true) + @PathParam("package-name") + String packageName); } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/ReportManagementService.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/ReportManagementService.java index eee16133815..6a33957f271 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/ReportManagementService.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/api/ReportManagementService.java @@ -35,6 +35,7 @@ import org.wso2.carbon.device.mgt.jaxrs.beans.DeviceList; import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse; import org.wso2.carbon.device.mgt.jaxrs.util.Constants; +import javax.validation.constraints.Size; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; @@ -369,4 +370,77 @@ public interface ReportManagementService { defaultValue = "5") @QueryParam("limit") int limit); + + @GET + @Path("/devices/{device-type}/{package-name}/not-installed") + @ApiOperation( + produces = MediaType.APPLICATION_JSON, + httpMethod = "GET", + value = "Getting Details of Application Not Installed Devices", + notes = "Provides details of all the devices enrolled with Entgra 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 = 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 getAppNotInstalledDevices( + @ApiParam( + name = "device-type", + value = "The device type name, such as ios, android, windows, or fire-alarm.", + required = true) + @PathParam("device-type") + String deviceType, + @ApiParam( + name = "package-name", + value = "The package name of the app.", + required = true) + @PathParam("package-name") + String packageName, + @ApiParam( + name = "app-version", + value = "Version of the app") + @QueryParam("app-version") String version, + @ApiParam( + name = "offset", + value = "The starting pagination index for the complete list of qualified items.", + defaultValue = "0") + @QueryParam("offset") + int offset, + @ApiParam( + name = "limit", + value = "Provide how many device details you require from the starting pagination index/offset.", + defaultValue = "5") + @QueryParam("limit") + int limit); } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java index dbbff69e9fe..cf697d57b29 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/DeviceManagementServiceImpl.java @@ -87,6 +87,7 @@ import org.wso2.carbon.device.mgt.jaxrs.beans.ErrorResponse; import org.wso2.carbon.device.mgt.jaxrs.beans.OperationList; import org.wso2.carbon.device.mgt.jaxrs.beans.OperationRequest; import org.wso2.carbon.device.mgt.jaxrs.beans.ComplianceDeviceList; +import org.wso2.carbon.device.mgt.jaxrs.beans.ApplicationList; import org.wso2.carbon.device.mgt.jaxrs.service.api.DeviceManagementService; import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.InputValidationException; import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.RequestValidationUtil; @@ -108,7 +109,6 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; import javax.ws.rs.DefaultValue; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -1106,4 +1106,58 @@ public class DeviceManagementServiceImpl implements DeviceManagementService { new ErrorResponse.ErrorResponseBuilder().setMessage(msg).build()).build(); } } + + @GET + @Override + @Path("/{device-type}/applications") + public Response getApplications( + @PathParam("device-type") String deviceType, + @DefaultValue("0") + @QueryParam("offset") int offset, + @DefaultValue("10") + @QueryParam("limit") int limit) { + PaginationRequest request = new PaginationRequest(offset, limit); + ApplicationList applicationList = new ApplicationList(); + request.setDeviceType(deviceType); + try { + PaginationResult paginationResult = DeviceMgtAPIUtils + .getDeviceManagementService() + .getApplications(request); + + if (paginationResult.getData().isEmpty()) { + return Response.status(Response.Status.OK) + .entity("No applications are available under " + deviceType + " platform.").build(); + } else { + applicationList.setList((List) paginationResult.getData()); + applicationList.setCount(paginationResult.getRecordsTotal()); + return Response.status(Response.Status.OK).entity(applicationList).build(); + } + } catch (DeviceTypeNotFoundException e) { + String msg = "Error occurred while retrieving application list." + + " Device type (Application Platform): " + deviceType + + "is not valid"; + log.error(msg); + return Response.status(Response.Status.NOT_FOUND).entity(msg).build(); + } catch (ApplicationManagementException e) { + String msg = "Error occurred while retrieving application list"; + log.error(msg, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build(); + } + } + + @GET + @Path("/application/{package-name}/versions") + @Override + public Response getAppVersions( + @PathParam("package-name") String packageName) { + try { + List versions = DeviceMgtAPIUtils.getDeviceManagementService() + .getAppVersions(packageName); + return Response.status(Response.Status.OK).entity(versions).build(); + } catch (ApplicationManagementException e) { + String msg = "Error occurred while retrieving version list for app with package name " + packageName; + log.error(msg, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build(); + } + } } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/ReportManagementServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/ReportManagementServiceImpl.java index 71948ec7cfe..212c73177f8 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/ReportManagementServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.api/src/main/java/org/wso2/carbon/device/mgt/jaxrs/service/impl/ReportManagementServiceImpl.java @@ -34,6 +34,7 @@ import org.wso2.carbon.device.mgt.jaxrs.service.api.ReportManagementService; import org.wso2.carbon.device.mgt.jaxrs.service.impl.util.RequestValidationUtil; import org.wso2.carbon.device.mgt.jaxrs.util.DeviceMgtAPIUtils; +import javax.validation.constraints.Size; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; @@ -197,4 +198,44 @@ public class ReportManagementServiceImpl implements ReportManagementService { return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build(); } } + + @GET + @Path("/devices/{device-type}/{package-name}/not-installed") + @Override + public Response getAppNotInstalledDevices( + @PathParam("device-type") String deviceType, + @PathParam("package-name") String packageName, + @QueryParam("app-version") String version, + @DefaultValue("0") + @QueryParam("offset") int offset, + @DefaultValue("10") + @QueryParam("limit") int limit) { + try { + RequestValidationUtil.validatePaginationParameters(offset, limit); + PaginationRequest request = new PaginationRequest(offset, limit); + DeviceList devices = new DeviceList(); + request.setDeviceType(deviceType); + + PaginationResult result = DeviceMgtAPIUtils.getReportManagementService() + .getAppNotInstalledDevices(request, packageName, version); + if (result.getData().isEmpty()) { + return Response.status(Response.Status.OK) + .entity("App with package name " + packageName + + " is installed in all enrolled devices").build(); + } else { + devices.setList((List) result.getData()); + devices.setCount(result.getRecordsTotal()); + return Response.status(Response.Status.OK).entity(devices).build(); + } + } catch (DeviceTypeNotFoundException e) { + String msg = "Error occurred while retrieving devices list. Device type: " + deviceType + + "is not valid"; + log.error(msg); + return Response.status(Response.Status.NOT_FOUND).entity(msg).build(); + } catch (ReportManagementException e) { + String msg = "Error occurred while retrieving device list"; + log.error(msg, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build(); + } + } } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/report/mgt/ReportManagementService.java b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/report/mgt/ReportManagementService.java index 700d79d7052..a8b24bae305 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/report/mgt/ReportManagementService.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.common/src/main/java/org/wso2/carbon/device/mgt/common/report/mgt/ReportManagementService.java @@ -20,6 +20,7 @@ package org.wso2.carbon.device.mgt.common.report.mgt; import com.google.gson.JsonObject; import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.PaginationResult; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; import org.wso2.carbon.device.mgt.common.exceptions.DeviceTypeNotFoundException; import org.wso2.carbon.device.mgt.common.exceptions.ReportManagementException; @@ -59,4 +60,17 @@ public interface ReportManagementService { */ PaginationResult getDevicesExpiredByOSVersion(PaginationRequest request) throws ReportManagementException, DeviceTypeNotFoundException; + + /** + * This method is used to get devices which have not installed the app with the given package name + * + * @param request Request object with device type + * @param packageName Package name of the application + * @param version Version of the application + * @return {@link PaginationResult} + * @throws ReportManagementException + * @throws DeviceTypeNotFoundException + */ + PaginationResult getAppNotInstalledDevices(PaginationRequest request, String packageName, String version) + throws ReportManagementException, DeviceTypeNotFoundException; } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/ApplicationDAO.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/ApplicationDAO.java index 1808d652da3..a84d4099584 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/ApplicationDAO.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/ApplicationDAO.java @@ -18,6 +18,8 @@ */ package org.wso2.carbon.device.mgt.core.dao; +import org.wso2.carbon.device.mgt.common.PaginationRequest; +import org.wso2.carbon.device.mgt.common.PaginationResult; import org.wso2.carbon.device.mgt.common.app.mgt.Application; import java.util.List; @@ -38,4 +40,25 @@ public interface ApplicationDAO { throws DeviceManagementDAOException; List getInstalledApplications(int deviceId, int enrolmentId) throws DeviceManagementDAOException; + + /** + * This method is used to get a list of applications installed in all enrolled devices + * + * @param request Request object with limit and offset + * @param tenantId ID of the current tenant + * @return List of {@link Application} objects + * @throws DeviceManagementDAOException If any database error occured + */ + List getApplications(PaginationRequest request, int tenantId) + throws DeviceManagementDAOException; + + /** + * This method is used to get a list of app versions when app package name is given. + * + * @param tenantId ID of the current tenant + * @param packageName Package name of the application + * @return String list of app versions + * @throws DeviceManagementDAOException If any database error occured + */ + List getAppVersions(int tenantId, String packageName) throws DeviceManagementDAOException; } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java index 3de294f8f54..2ffd9d84518 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/DeviceDAO.java @@ -634,4 +634,34 @@ public interface DeviceDAO { */ int getCountOfDeviceExpiredByOSVersion(String deviceType, long osBuildDate, int tenantId) throws DeviceManagementDAOException; + + /** + * This method is used to get devices which have not installed the app with the given package name + * + * @param request Request object with device type + * @param tenantId ID of the current tenant + * @param packageName Package name of the application + * @param version Version of the application + * @return A list of device objects + * @throws DeviceManagementDAOException Thrown if error occurs while database transactions + */ + List getAppNotInstalledDevices(PaginationRequest request, + int tenantId, + String packageName, + String version) throws DeviceManagementDAOException; + + /** + * This method is used to get count if devices which have not installed the app with the given package name + * + * @param request Request object with device type + * @param tenantId ID of the current tenant + * @param packageName Package name of the application + * @param version Version of the application + * @return Device count + * @throws DeviceManagementDAOException Thrown if error occurs while database transactions + */ + int getCountOfAppNotInstalledDevices(PaginationRequest request, + int tenantId, + String packageName, + String version) throws DeviceManagementDAOException; } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractApplicationDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractApplicationDAOImpl.java index ef37c222364..31a81e6ca0b 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractApplicationDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractApplicationDAOImpl.java @@ -20,6 +20,7 @@ package org.wso2.carbon.device.mgt.core.dao.impl; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.app.mgt.Application; import org.wso2.carbon.device.mgt.core.dao.ApplicationDAO; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException; @@ -271,6 +272,81 @@ public abstract class AbstractApplicationDAOImpl implements ApplicationDAO { return applications; } + @Override + public List getApplications(PaginationRequest request, int tenantId) + throws DeviceManagementDAOException { + List applications = new ArrayList<>(); + Application application; + String sql = "Select " + + "ID," + + " NAME, " + + "APP_IDENTIFIER, " + + "PLATFORM, " + + "CATEGORY, " + + "VERSION, " + + "TYPE, " + + "LOCATION_URL, " + + "IMAGE_URL, " + + "APP_PROPERTIES, " + + "MEMORY_USAGE, " + + "IS_ACTIVE, " + + "TENANT_ID " + + "From DM_APPLICATION " + + "WHERE PLATFORM = ? " + + "AND TENANT_ID = ? LIMIT ? OFFSET ?"; + try { + Connection conn = this.getConnection(); + try (PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, request.getDeviceType()); + stmt.setInt(2, tenantId); + stmt.setInt(3, request.getRowCount()); + stmt.setInt(4, request.getStartIndex()); + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + application = loadApplication(rs); + applications.add(application); + } + } + } + } catch (SQLException e) { + String msg = "SQL Error occurred while retrieving the list of Applications " + + "installed in all enrolled devices for device type " + request.getDeviceType() + + " under tenant id " + tenantId; + log.error(msg, e); + throw new DeviceManagementDAOException(msg, e); + } + return applications; + } + + @Override + public List getAppVersions(int tenantId, String packageName) throws DeviceManagementDAOException { + String sql = "SELECT " + + "VERSION " + + "FROM DM_APPLICATION " + + "WHERE TENANT_ID=? " + + "AND APP_IDENTIFIER=?"; + try { + Connection conn = this.getConnection(); + try (PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setInt(1, tenantId); + stmt.setString(2, packageName); + + try (ResultSet rs = stmt.executeQuery()) { + List versions = new ArrayList<>(); + while (rs.next()) { + versions.add(rs.getString("VERSION")); + } + return versions; + } + } + } catch (SQLException e) { + String msg = "Error occurred while retrieving information of all " + + "registered apps under tenant id " + tenantId; + log.error(msg, e); + throw new DeviceManagementDAOException(msg, e); + } + } + private Application loadApplication(ResultSet rs) throws DeviceManagementDAOException { ByteArrayInputStream bais; ObjectInputStream ois; diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractDeviceDAOImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractDeviceDAOImpl.java index 97db197a1b2..81fd4c7ce3e 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractDeviceDAOImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/dao/impl/AbstractDeviceDAOImpl.java @@ -1894,6 +1894,123 @@ public abstract class AbstractDeviceDAOImpl implements DeviceDAO { } } + @Override + public List getAppNotInstalledDevices( + PaginationRequest request, int tenantId, String packageName, String version) + throws DeviceManagementDAOException { + List devices; + String deviceType = request.getDeviceType(); + boolean isVersionProvided = false; + + String sql = "SELECT " + + "d.ID AS DEVICE_ID, " + + "d.DESCRIPTION,d.NAME AS DEVICE_NAME, " + + "t.NAME AS DEVICE_TYPE, " + + "d.DEVICE_IDENTIFICATION, " + + "e.OWNER, " + + "e.OWNERSHIP, " + + "e.STATUS, " + + "e.DATE_OF_LAST_UPDATE, " + + "e.DATE_OF_ENROLMENT, " + + "e.ID AS ENROLMENT_ID " + + "FROM DM_DEVICE AS d " + + "INNER JOIN DM_ENROLMENT AS e ON d.ID = e.DEVICE_ID " + + "INNER JOIN DM_DEVICE_TYPE AS t ON d.DEVICE_TYPE_ID = t.ID " + + "WHERE t.NAME = ? AND e.TENANT_ID = ? AND d.ID " + + "NOT IN (SELECT m.DEVICE_ID " + + "FROM DM_DEVICE_APPLICATION_MAPPING AS m " + + "INNER JOIN DM_APPLICATION AS a ON m.APPLICATION_ID=a.ID " + + "WHERE a.APP_IDENTIFIER = ?"; + + if (!StringUtils.isBlank(version)) { + sql = sql + " AND a.VERSION = ? "; + isVersionProvided = true; + } + + sql = sql + ") LIMIT ? OFFSET ?"; + + try { + Connection conn = this.getConnection(); + try (PreparedStatement stmt = conn.prepareStatement(sql)) { + int paramIdx = 1; + stmt.setString(paramIdx++, deviceType); + stmt.setInt(paramIdx++, tenantId); + stmt.setString(paramIdx++, packageName); + if (isVersionProvided) { + stmt.setString(paramIdx++, version); + } + stmt.setInt(paramIdx++, request.getRowCount()); + stmt.setInt(paramIdx, request.getStartIndex()); + + try (ResultSet rs = stmt.executeQuery()) { + devices = new ArrayList<>(); + while (rs.next()) { + Device device = DeviceManagementDAOUtil.loadDevice(rs); + devices.add(device); + } + return devices; + } + } + } 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); + } + } + + @Override + public int getCountOfAppNotInstalledDevices( + PaginationRequest request, int tenantId, String packageName, String version) + throws DeviceManagementDAOException { + String deviceType = request.getDeviceType(); + boolean isVersionProvided = false; + + String sql = "SELECT " + + "COUNT(d.ID) AS DEVICE_COUNT " + + "FROM DM_DEVICE AS d " + + "INNER JOIN DM_ENROLMENT AS e ON d.ID = e.DEVICE_ID " + + "INNER JOIN DM_DEVICE_TYPE AS t ON d.DEVICE_TYPE_ID = t.ID " + + "WHERE t.NAME = ? AND e.TENANT_ID = ? AND d.ID " + + "NOT IN " + + "(SELECT m.DEVICE_ID " + + "FROM DM_DEVICE_APPLICATION_MAPPING AS m " + + "INNER JOIN DM_APPLICATION AS a ON m.APPLICATION_ID=a.ID " + + "WHERE a.APP_IDENTIFIER = ?"; + + if (!StringUtils.isBlank(version)) { + sql = sql + " AND a.VERSION = ? "; + isVersionProvided = true; + } + + sql = sql + ")"; + + try { + Connection conn = this.getConnection(); + try (PreparedStatement stmt = conn.prepareStatement(sql)) { + int paramIdx = 1; + stmt.setString(paramIdx++, deviceType); + stmt.setInt(paramIdx++, tenantId); + stmt.setString(paramIdx++, packageName); + if (isVersionProvided) { + stmt.setString(paramIdx, version); + } + try (ResultSet rs = stmt.executeQuery()) { + int deviceCount = 0; + if (rs.next()) { + deviceCount = rs.getInt("DEVICE_COUNT"); + } + return deviceCount; + } + } + } 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); + } + } + /*** * This method removes records of a given list of devices from the DM_DEVICE_DETAIL table * @param conn Connection object diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/ReportManagementServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/ReportManagementServiceImpl.java index 5d408447f53..06269c2cef1 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/ReportManagementServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/report/mgt/ReportManagementServiceImpl.java @@ -23,12 +23,15 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.device.mgt.common.Count; import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.app.mgt.Application; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException; import org.wso2.carbon.device.mgt.common.PaginationRequest; import org.wso2.carbon.device.mgt.common.PaginationResult; import org.wso2.carbon.device.mgt.common.exceptions.DeviceTypeNotFoundException; import org.wso2.carbon.device.mgt.common.exceptions.ReportManagementException; import org.wso2.carbon.device.mgt.common.report.mgt.ReportManagementService; +import org.wso2.carbon.device.mgt.core.dao.ApplicationDAO; import org.wso2.carbon.device.mgt.core.dao.DeviceDAO; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOFactory; @@ -39,7 +42,6 @@ import org.wso2.carbon.device.mgt.core.util.DeviceManagerUtil; import java.sql.SQLException; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Calendar; @@ -54,9 +56,11 @@ public class ReportManagementServiceImpl implements ReportManagementService { private static final Log log = LogFactory.getLog(ReportManagementServiceImpl.class); private DeviceDAO deviceDAO; + private ApplicationDAO applicationDAO; public ReportManagementServiceImpl() { this.deviceDAO = DeviceManagementDAOFactory.getDeviceDAO(); + this.applicationDAO = DeviceManagementDAOFactory.getApplicationDAO(); } @Override @@ -270,4 +274,62 @@ public class ReportManagementServiceImpl implements ReportManagementService { } return resultObject; } + + @Override + public PaginationResult getAppNotInstalledDevices(PaginationRequest request, String packageName, String version) + throws ReportManagementException, DeviceTypeNotFoundException { + PaginationResult paginationResult = new PaginationResult(); + if(StringUtils.isBlank(packageName)){ + String msg = "Error, application package name is not given"; + log.error(msg); + throw new ReportManagementException(msg); + } + try { + int tenantId = DeviceManagementDAOUtil.getTenantId(); + request = DeviceManagerUtil.validateDeviceListPageSize(request); + + String deviceType = request.getDeviceType(); + DeviceType deviceTypeObj = DeviceManagerUtil.getDeviceType( + deviceType, tenantId); + if (deviceTypeObj == null) { + String msg = "Error, device of type: " + deviceType + " does not exist"; + log.error(msg); + throw new DeviceTypeNotFoundException(msg); + } + + try { + DeviceManagementDAOFactory.openConnection(); + List devices = deviceDAO.getAppNotInstalledDevices( + request, + tenantId, + packageName, + version + ); + paginationResult.setData(devices); + int deviceCount = deviceDAO.getCountOfAppNotInstalledDevices( + request, + tenantId, + packageName, + version); + paginationResult.setRecordsTotal(deviceCount); + return paginationResult; + } 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(); + } + + } catch (DeviceManagementException e) { + String msg = "Error occurred while validating device list page size"; + log.error(msg, e); + throw new ReportManagementException(msg, e); + } catch (DeviceManagementDAOException e) { + String msg = "Error occurred while retrieving Tenant ID"; + log.error(msg, e); + throw new ReportManagementException(msg, e); + } + } } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderService.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderService.java index 346264755e4..15667409f74 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderService.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderService.java @@ -45,6 +45,7 @@ import org.wso2.carbon.device.mgt.common.DeviceTransferRequest; import org.wso2.carbon.device.mgt.common.MonitoringOperation; import org.wso2.carbon.device.mgt.common.StartupOperationConfig; import org.wso2.carbon.device.mgt.common.OperationMonitoringTaskConfig; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException; import org.wso2.carbon.device.mgt.common.exceptions.DeviceNotFoundException; import org.wso2.carbon.device.mgt.common.exceptions.DeviceTypeNotFoundException; @@ -855,4 +856,23 @@ public interface DeviceManagementProviderService { */ PaginationResult getAppSubscribedDevices(int offsetValue, int limitValue, List devicesIds, String status) throws DeviceManagementException; + + /** + * This method is used to get a list of applications installed in all enrolled devices + * + * @param request Request object with limit and offset + * @return {@link PaginationResult} + * @throws ApplicationManagementException if any service level or DAO level error occurs. + */ + PaginationResult getApplications(PaginationRequest request) + throws ApplicationManagementException, DeviceTypeNotFoundException; + + /** + * This method is used to get a list of app versions when app package name is given. + * + * @param packageName Package name of the application + * @return String list of app versions + * @throws ApplicationManagementException if any service level or DAO level error occurs. + */ + List getAppVersions(String packageName) throws ApplicationManagementException; } diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderServiceImpl.java b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderServiceImpl.java index a5da653b0f2..60ebdb7ab64 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderServiceImpl.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.core/src/main/java/org/wso2/carbon/device/mgt/core/service/DeviceManagementProviderServiceImpl.java @@ -63,6 +63,7 @@ import org.wso2.carbon.device.mgt.common.DeviceTransferRequest; import org.wso2.carbon.device.mgt.common.DevicePropertyNotification; import org.wso2.carbon.device.mgt.common.DeviceEnrollmentInfoNotification; import org.wso2.carbon.device.mgt.common.DeviceNotification; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; import org.wso2.carbon.device.mgt.common.exceptions.DeviceManagementException; import org.wso2.carbon.device.mgt.common.exceptions.DeviceNotFoundException; import org.wso2.carbon.device.mgt.common.exceptions.DeviceTypeNotFoundException; @@ -114,6 +115,7 @@ import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOException; import org.wso2.carbon.device.mgt.core.dao.DeviceManagementDAOFactory; import org.wso2.carbon.device.mgt.core.dao.DeviceTypeDAO; import org.wso2.carbon.device.mgt.core.dao.EnrollmentDAO; +import org.wso2.carbon.device.mgt.core.dao.util.DeviceManagementDAOUtil; import org.wso2.carbon.device.mgt.core.device.details.mgt.DeviceInformationManager; import org.wso2.carbon.device.mgt.core.device.details.mgt.dao.DeviceDetailsDAO; import org.wso2.carbon.device.mgt.core.device.details.mgt.dao.DeviceDetailsMgtDAOException; @@ -3967,6 +3969,76 @@ public class DeviceManagementProviderServiceImpl implements DeviceManagementProv return paginationResult; } + @Override + public PaginationResult getApplications(PaginationRequest request) + throws ApplicationManagementException, DeviceTypeNotFoundException { + PaginationResult paginationResult = new PaginationResult(); + try { + int tenantId = DeviceManagementDAOUtil.getTenantId(); + request = DeviceManagerUtil.validateDeviceListPageSize(request); + + String deviceType = request.getDeviceType(); + DeviceType deviceTypeObj = DeviceManagerUtil.getDeviceType( + deviceType, tenantId); + if (deviceTypeObj == null) { + String msg = "Error, device of type (application platform): " + deviceType + " does not exist"; + log.error(msg); + throw new DeviceTypeNotFoundException(msg); + } + + try { + DeviceManagementDAOFactory.openConnection(); + List applicationList = applicationDAO.getApplications( + request, + tenantId + ); + paginationResult.setData(applicationList); + paginationResult.setRecordsTotal(applicationList.size()); + return paginationResult; + } catch (SQLException e) { + String msg = "Error occurred while opening a connection " + + "to the data source"; + log.error(msg, e); + throw new ApplicationManagementException(msg, e); + } finally { + DeviceManagementDAOFactory.closeConnection(); + } + + } catch (DeviceManagementException e) { + String msg = "Error occurred while validating device list page size"; + log.error(msg, e); + throw new ApplicationManagementException(msg, e); + } catch (DeviceManagementDAOException e) { + String msg = "Error occurred while retrieving Tenant ID"; + log.error(msg, e); + throw new ApplicationManagementException(msg, e); + } + } + + @Override + public List getAppVersions(String packageName) + throws ApplicationManagementException { + try { + DeviceManagementDAOFactory.openConnection(); + List versions = applicationDAO.getAppVersions( + DeviceManagementDAOUtil.getTenantId(), + packageName + ); + return versions; + } catch (SQLException e) { + String msg = "Error occurred while opening a connection " + + "to the data source"; + log.error(msg, e); + throw new ApplicationManagementException(msg, e); + } catch (DeviceManagementDAOException e) { + String msg = "Error occurred while retrieving Tenant ID"; + log.error(msg, e); + throw new ApplicationManagementException(msg, e); + } finally { + DeviceManagementDAOFactory.closeConnection(); + } + } + /** * Wrap the device configuration data into DeviceConfiguration bean * @param device Device queried using the properties