diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/pom.xml b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/pom.xml index 395ab40ed6..178ba50fba 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/pom.xml +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/pom.xml @@ -79,6 +79,16 @@ install + + lint + + npm + + + run-script lint + + generate-resources + prod diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/.eslintrc b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/.eslintrc new file mode 100644 index 0000000000..9ea2b2a401 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/.eslintrc @@ -0,0 +1,325 @@ +{ + "parser": "babel-eslint", + "plugins": [ + "react", + "babel", + "jsx", + "prettier" + ], + "extends": [ + "eslint:recommended", + "plugin:react/recommended" + ], + "parserOptions": { + "ecmaVersion": 2016, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "settings": { + "react": { + "createClass": "createReactClass", + "pragma": "React", + "version": "16.8.6" + } + }, + "env": { + "node": true, + "commonjs": true, + "browser": true, + "jasmine": true, + "es6": true + }, + "globals": { + "document": true, + "console": true, + // Only for development purposes + "setTimeout": true, + "window" : true + }, + "rules": { + "prettier/prettier": "error", + // Enforce the spacing around the * in generator functions. + "generator-star-spacing": [2, "after"], + // Disallow using variables outside the blocks they are defined (especially + // since only let and const are used, see "no-var"). + "block-scoped-var": 2, + // Require camel case names + "camelcase": 2, + // Allow trailing commas for easy list extension. Having them does not + // impair readability, but also not required either. + "comma-dangle": 0, + // Warn about cyclomatic complexity in functions. + "complexity": 1, + // Don't warn for inconsistent naming when capturing this (not so important + // with auto-binding fat arrow functions). + "consistent-this": 0, + // Enforce curly brace conventions for all control statements. + "curly": 2, + // Don't require a default case in switch statements. Avoid being forced to + // add a bogus default when you know all possible cases are handled. + "default-case": 0, + // Encourage the use of dot notation whenever possible. + "dot-notation": 2, + // Allow mixed 'LF' and 'CRLF' as linebreaks. + "linebreak-style": 0, + // Don't enforce the maximum depth that blocks can be nested. + "max-depth": 0, + // Maximum length of a line. + "max-len": [2, 100, 2, { "ignoreStrings": true, "ignoreUrls": true}], + // Maximum depth callbacks can be nested. + "max-nested-callbacks": [2, 3], + // Don't limit the number of parameters that can be used in a function. + "max-params": 0, + // Don't limit the maximum number of statement allowed in a function. + "max-statements": 0, + // Require a capital letter for constructors, only check if all new + // operators are followed by a capital letter. Don't warn when capitalized + // functions are used without the new operator. + "new-cap": [2, {"capIsNew": false}], + // Disallow use of the Array constructor. + "no-array-constructor": 2, + // Allow use of bitwise operators. + "no-bitwise": 0, + // Disallow use of arguments.caller or arguments.callee. + "no-caller": 2, + // Disallow the catch clause parameter name being the same as a variable in + // the outer scope, to avoid confusion. + "no-catch-shadow": 2, + // Disallow assignment in conditional expressions. + "no-cond-assign": 2, + // Allow using the console API. + "no-console": 0, + // Allow using constant expressions in conditions like while (true) + "no-constant-condition": 0, + // Allow use of the continue statement. + "no-continue": 0, + // Disallow control characters in regular expressions. + "no-control-regex": 2, + // Disallow deletion of variables (deleting properties is fine). + "no-delete-var": 2, + // Disallow duplicate arguments in functions. + "no-dupe-args": 2, + // Disallow duplicate keys when creating object literals. + "no-dupe-keys": 2, + // Disallow multiple empty lines + "no-multiple-empty-lines": "error", + // Disallow a duplicate case label. + "no-duplicate-case": 2, + // Disallow else after a return in an if. The else around the second return + // here is useless: + // if (something) { return false; } else { return true; } + "no-else-return": 2, + // Disallow empty statements. This will report an error for: + // try { something(); } catch (e) {} + // but will not report it for: + // try { something(); } catch (e) { /* Silencing the error because ...*/ } + // which is a valid use case. + "no-empty": 2, + // Disallow the use of empty character classes in regular expressions. + "no-empty-character-class": 2, + // Disallow use of labels for anything other then loops and switches. + "no-labels": 2, + // Disallow use of eval(). We have other APIs to evaluate code in content. + "no-eval": 2, + // Disallow assigning to the exception in a catch block. + "no-ex-assign": 2, + // Disallow adding to native types + "no-extend-native": 2, + // Disallow unnecessary function binding. + "no-extra-bind": 2, + // Disallow double-negation boolean casts in a boolean context. + "no-extra-boolean-cast": 2, + // Allow unnecessary parentheses, as they may make the code more readable. + "no-extra-parens": 0, + // Disallow fallthrough of case statements, except if there is a comment. + "no-fallthrough": 2, + // Allow the use of leading or trailing decimal points in numeric literals. + "no-floating-decimal": 0, + // Disallow if as the only statement in an else block. + "no-lonely-if": 2, + // Disallow use of multiline strings (use template strings instead). + "no-multi-str": 2, + // Disallow reassignments of native objects. + "no-native-reassign": 2, + // Disallow nested ternary expressions, they make the code hard to read. + "no-nested-ternary": 2, + // Allow use of new operator with the require function. + "no-new-require": 0, + // Disallow use of octal literals. + "no-octal": 2, + // Allow reassignment of function parameters. + "no-param-reassign": 0, + // Allow string concatenation with __dirname and __filename (not a node env). + "no-path-concat": 0, + // Allow use of unary operators, ++ and --. + "no-plusplus": 0, + // Allow using process.env (not a node environment). + "no-process-env": 0, + // Allow using process.exit (not a node environment). + "no-process-exit": 0, + // Disallow usage of __proto__ property. + "no-proto": 2, + // Disallow declaring the same variable more than once (we use let anyway). + "no-redeclare": 2, + // Disallow multiple spaces in a regular expression literal. + "no-regex-spaces": 2, + // Allow reserved words being used as object literal keys. + "no-reserved-keys": 0, + // Don't restrict usage of specified node modules (not a node environment). + "no-restricted-modules": 0, + // Disallow use of assignment in return statement. It is preferable for a + // single line of code to have only one easily predictable effect. + "no-return-assign": 2, + // Allow use of javascript: urls. + "no-script-url": 0, + // Disallow comparisons where both sides are exactly the same. + "no-self-compare": 2, + // Disallow use of comma operator. + "no-sequences": 2, + // Warn about declaration of variables already declared in the outer scope. + // This isn't an error because it sometimes is useful to use the same name + // in a small helper function rather than having to come up with another + // random name. + // Still, making this a warning can help people avoid being confused. + "no-shadow": 0, + // Require empty line at end of file + "eol-last": "error", + // Disallow shadowing of names such as arguments. + "no-shadow-restricted-names": 2, + "no-space-before-semi": 0, + // Disallow sparse arrays, eg. let arr = [,,2]. + // Array destructuring is fine though: + // for (let [, breakpointPromise] of aPromises) + "no-sparse-arrays": 2, + // Allow use of synchronous methods (not a node environment). + "no-sync": 0, + // Allow the use of ternary operators. + "no-ternary": 0, + // Don't allow spaces after end of line + "no-trailing-spaces": "error", + // Disallow throwing literals (eg. throw "error" instead of + // throw new Error("error")). + "no-throw-literal": 2, + // Disallow use of undeclared variables unless mentioned in a /*global */ + // block. Note that globals from head.js are automatically imported in tests + // by the import-headjs-globals rule form the mozilla eslint plugin. + "no-undef": 2, + // Allow use of undefined variable. + "no-undefined": 0, + // Disallow the use of Boolean literals in conditional expressions. + "no-unneeded-ternary": 2, + // Disallow unreachable statements after a return, throw, continue, or break + // statement. + "no-unreachable": 2, + // Allow using variables before they are defined. + "no-unused-vars": [2, {"vars": "all", "args": "none"}], + // Disallow global and local variables that arent used, but allow unused function arguments. + "no-use-before-define": 0, + // We use var-only-at-top-level instead of no-var as we allow top level + // vars. + "no-var": 0, + // Allow using TODO/FIXME comments. + "no-warning-comments": 0, + // Disallow use of the with statement. + "no-with": 2, + // Dont require method and property shorthand syntax for object literals. + // We use this in the code a lot, but not consistently, and this seems more + // like something to check at code review time. + "object-shorthand": 0, + // Allow more than one variable declaration per function. + "one-var": 0, + // Single quotes should be used. + "quotes": [2, "single", "avoid-escape"], + // Require use of the second argument for parseInt(). + "radix": 2, + // Dont require to sort variables within the same declaration block. + // Anyway, one-var is disabled. + "sort-vars": 0, + "space-after-function-name": 0, + "space-before-function-parentheses": 0, + // Disallow space before function opening parenthesis. + //"space-before-function-paren": [2, "never"], + // Disable the rule that checks if spaces inside {} and [] are there or not. + // Our code is split on conventions, and itd be nice to have 2 rules + // instead, one for [] and one for {}. So, disabling until we write them. + "space-in-brackets": 0, + // Deprecated, will be removed in 1.0. + "space-unary-word-ops": 0, + // Require a space immediately following the // in a line comment. + "spaced-comment": [2, "always"], + // Require "use strict" to be defined globally in the script. + "strict": [2, "global"], + // Disallow comparisons with the value NaN. + "use-isnan": 2, + // Warn about invalid JSDoc comments. + // Disabled for now because of https://github.com/eslint/eslint/issues/2270 + // The rule fails on some jsdoc comments like in: + // devtools/client/webconsole/console-output.js + "valid-jsdoc": 0, + // Ensure that the results of typeof are compared against a valid string. + "valid-typeof": 2, + // Allow vars to be declared anywhere in the scope. + "vars-on-top": 0, + // Dont require immediate function invocation to be wrapped in parentheses. + "wrap-iife": 0, + // Don't require regex literals to be wrapped in parentheses (which + // supposedly prevent them from being mistaken for division operators). + "wrap-regex": 0, + // Require for-in loops to have an if statement. + "guard-for-in": 0, + // allow/disallow an empty newline after var statement + "newline-after-var": 0, + // disallow the use of alert, confirm, and prompt + "no-alert": 0, + // disallow the use of deprecated react changes and lifecycle methods + "react/no-deprecated": 0, + // disallow comparisons to null without a type-checking operator + "no-eq-null": 0, + // disallow overwriting functions written as function declarations + "no-func-assign": 0, + // disallow use of eval()-like methods + "no-implied-eval": 0, + // disallow function or variable declarations in nested blocks + "no-inner-declarations": 0, + // disallow invalid regular expression strings in the RegExp constructor + "no-invalid-regexp": 0, + // disallow irregular whitespace outside of strings and comments + "no-irregular-whitespace": 0, + // disallow unnecessary nested blocks + "no-lone-blocks": 0, + // disallow creation of functions within loops + "no-loop-func": 0, + // disallow use of new operator when not part of the assignment or + // comparison + "no-new": 0, + // disallow use of new operator for Function object + "no-new-func": 0, + // disallow use of the Object constructor + "no-new-object": 0, + // disallows creating new instances of String,Number, and Boolean + "no-new-wrappers": 0, + // disallow the use of object properties of the global object (Math and + // JSON) as functions + "no-obj-calls": 0, + // disallow use of octal escape sequences in string literals, such as + // var foo = "Copyright \251"; + "no-octal-escape": 0, + // disallow use of undefined when initializing variables + "no-undef-init": 0, + // disallow usage of expressions in statement position + "no-unused-expressions": 0, + // disallow use of void operator + "no-void": 0, + // disallow wrapping of non-IIFE statements in parens + "no-wrap-func": 0, + // require assignment operator shorthand where possible or prohibit it + // entirely + "operator-assignment": 0, + // enforce operators to be placed before or after line breaks + "operator-linebreak": 0, + // disable chacking prop types + "react/prop-types": 0 + } +} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/.prettierrc b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/.prettierrc new file mode 100644 index 0000000000..d281e2bd55 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "parser": "flow" +} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/babel.config.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/babel.config.js index 7ec6d0936b..9cfcaf0bd9 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/babel.config.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/babel.config.js @@ -16,14 +16,13 @@ * under the License. */ -module.exports = function (api) { +module.exports = function(api) { api.cache(true); - const presets = [ "@babel/preset-env", - "@babel/preset-react" ]; - const plugins = ["@babel/plugin-proposal-class-properties"]; + const presets = ['@babel/preset-env', '@babel/preset-react']; + const plugins = ['@babel/plugin-proposal-class-properties']; return { presets, - plugins + plugins, }; -}; \ No newline at end of file +}; diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/package.json b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/package.json index dbba25e052..f0002c6fea 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/package.json +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/package.json @@ -56,6 +56,12 @@ "body-parser": "^1.19.0", "chai": "^4.1.2", "css-loader": "^0.28.11", + "eslint": "^5.16.0", + "eslint-config-prettier": "4.3.0", + "eslint-plugin-babel": "5.3.0", + "eslint-plugin-jsx": "0.0.2", + "eslint-plugin-prettier": "3.1.0", + "eslint-plugin-react": "7.14.2", "express": "^4.17.1", "express-pino-logger": "^4.0.0", "file-loader": "^2.0.0", @@ -74,6 +80,7 @@ "npm-run-all": "^4.1.5", "pino-colada": "^1.4.5", "postcss-loader": "^3.0.0", + "prettier": "1.18.1", "react": "^16.8.6", "react-dom": "^16.8.6", "react-intl": "^2.9.0", @@ -96,6 +103,7 @@ "build_prod": "NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 webpack -p --display errors-only --hide-modules", "build_dev": "NODE_ENV=development webpack -d --watch ", "server": "node-env-run server --exec nodemon | pino-colada", - "dev2": "run-p server start" + "dev2": "run-p server start", + "lint": "eslint \"src/**/*.js\"" } } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/App.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/App.js index 69ecede98c..fcb81a1501 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/App.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/App.js @@ -16,155 +16,168 @@ * under the License. */ -import React from "react"; -import "antd/dist/antd.less"; -import RouteWithSubRoutes from "./components/RouteWithSubRoutes"; -import { - BrowserRouter as Router, - Redirect, Switch, -} from 'react-router-dom'; -import axios from "axios"; -import {Layout, Spin, Result, notification} from "antd"; -import ConfigContext from "./context/ConfigContext"; +import React from 'react'; +import 'antd/dist/antd.less'; +import RouteWithSubRoutes from './components/RouteWithSubRoutes'; +import { BrowserRouter as Router, Redirect, Switch } from 'react-router-dom'; +import axios from 'axios'; +import { Layout, Spin, Result } from 'antd'; +import ConfigContext from './context/ConfigContext'; -const {Content} = Layout; +const { Content } = Layout; const loadingView = ( - - - - - + + + + + ); const errorView = ( - + ); class App extends React.Component { + constructor(props) { + super(props); + this.state = { + loading: true, + error: false, + config: {}, + }; + } - constructor(props) { - super(props); - this.state = { - loading: true, - error: false, - config: {} - } - } - - componentDidMount() { - this.updateFavicon(); - axios.get( - window.location.origin + "/publisher/public/conf/config.json", - ).then(res => { - const config = res.data; - this.checkUserLoggedIn(config); - }).catch((error) => { - this.setState({ - loading: false, - error: true - }) + componentDidMount() { + this.updateFavicon(); + axios + .get(window.location.origin + '/publisher/public/conf/config.json') + .then(res => { + const config = res.data; + this.checkUserLoggedIn(config); + }) + .catch(error => { + this.setState({ + loading: false, + error: true, }); - } + }); + } - getAndroidEnterpriseToken = (config) => { - axios.get( - window.location.origin + config.serverConfig.invoker.uri + - "/device-mgt/android/v1.0/enterprise/store-url?approveApps=true" + - "&searchEnabled=true&isPrivateAppsEnabled=true&isWebAppEnabled=true&isOrganizeAppPageVisible=true&isManagedConfigEnabled=true" + - "&host=" + window.location.origin, - ).then(res => { - config.androidEnterpriseToken = res.data.data.token; - this.setState({ - loading: false, - config: config - }); - }).catch((error) => { - config.androidEnterpriseToken = null; - this.setState({ - loading: false, - config: config - }) + getAndroidEnterpriseToken = config => { + axios + .get( + window.location.origin + + config.serverConfig.invoker.uri + + '/device-mgt/android/v1.0/enterprise/store-url?approveApps=true' + + '&searchEnabled=true&isPrivateAppsEnabled=true&isWebAppEnabled=true&isOrganizeAppPageVisible=true&isManagedConfigEnabled=true' + + '&host=' + + window.location.origin, + ) + .then(res => { + config.androidEnterpriseToken = res.data.data.token; + this.setState({ + loading: false, + config: config, }); - }; - - checkUserLoggedIn = (config) => { - axios.post( - window.location.origin + "/publisher-ui-request-handler/user", - "platform=publisher" - ).then(res => { - config.user = res.data.data; - const pageURL = window.location.pathname; - const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1); - if (lastURLSegment === "login") { - window.location.href = window.location.origin + `/publisher/`; - } else { - this.getAndroidEnterpriseToken(config); - } - }).catch((error) => { - if (error.hasOwnProperty("response") && error.response.status === 401) { - const redirectUrl = encodeURI(window.location.href); - const pageURL = window.location.pathname; - const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1); - if (lastURLSegment !== "login") { - window.location.href = window.location.origin + `/publisher/login?redirect=${redirectUrl}`; - } else { - this.getAndroidEnterpriseToken(config); - } - } else { - this.setState({ - loading: false, - error: true - }) - } + }) + .catch(error => { + config.androidEnterpriseToken = null; + this.setState({ + loading: false, + config: config, }); - }; + }); + }; - updateFavicon = () =>{ - const link = document.querySelector("link[rel*='icon']") || document.createElement('link'); - link.type = 'image/x-icon'; - link.rel = 'shortcut icon'; - link.href = window.location.origin+'/devicemgt/public/uuf.unit.favicon/img/favicon.png'; - document.getElementsByTagName('head')[0].appendChild(link); - }; + checkUserLoggedIn = config => { + axios + .post( + window.location.origin + '/publisher-ui-request-handler/user', + 'platform=publisher', + ) + .then(res => { + config.user = res.data.data; + const pageURL = window.location.pathname; + const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1); + if (lastURLSegment === 'login') { + window.location.href = window.location.origin + '/publisher/'; + } else { + this.getAndroidEnterpriseToken(config); + } + }) + .catch(error => { + if (error.hasOwnProperty('response') && error.response.status === 401) { + const redirectUrl = encodeURI(window.location.href); + const pageURL = window.location.pathname; + const lastURLSegment = pageURL.substr(pageURL.lastIndexOf('/') + 1); + if (lastURLSegment !== 'login') { + window.location.href = + window.location.origin + + `/publisher/login?redirect=${redirectUrl}`; + } else { + this.getAndroidEnterpriseToken(config); + } + } else { + this.setState({ + loading: false, + error: true, + }); + } + }); + }; + + updateFavicon = () => { + const link = + document.querySelector("link[rel*='icon']") || + document.createElement('link'); + link.type = 'image/x-icon'; + link.rel = 'shortcut icon'; + link.href = + window.location.origin + + '/devicemgt/public/uuf.unit.favicon/img/favicon.png'; + document.getElementsByTagName('head')[0].appendChild(link); + }; - render() { - const {loading, error} = this.state; + render() { + const { loading, error } = this.state; - const applicationView = ( - - -
- - - {this.props.routes.map((route) => ( - - ))} - -
-
-
- ); + const applicationView = ( + + +
+ + + {this.props.routes.map(route => ( + + ))} + +
+
+
+ ); - return ( -
- {loading && loadingView} - {!loading && !error && applicationView} - {error && errorView} -
- ); - } + return ( +
+ {loading && loadingView} + {!loading && !error && applicationView} + {error && errorView} +
+ ); + } } export default App; diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/RouteWithSubRoutes.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/RouteWithSubRoutes.js index 2dc80f8145..0b11230cc0 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/RouteWithSubRoutes.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/RouteWithSubRoutes.js @@ -17,21 +17,24 @@ */ import React from 'react'; -import {Route} from 'react-router-dom'; -class RouteWithSubRoutes extends React.Component{ - props; - constructor(props){ - super(props); - this.props = props; - } - render() { - return( - ( - - )}/> - ); - } - +import { Route } from 'react-router-dom'; +class RouteWithSubRoutes extends React.Component { + props; + constructor(props) { + super(props); + this.props = props; + } + render() { + return ( + ( + + )} + /> + ); + } } -export default RouteWithSubRoutes; \ No newline at end of file +export default RouteWithSubRoutes; diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/detailed-rating/DetailedRating.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/detailed-rating/DetailedRating.js index dba024f2ed..3bfbc81459 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/detailed-rating/DetailedRating.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/detailed-rating/DetailedRating.js @@ -16,124 +16,161 @@ * under the License. */ -import React from "react"; -import {Row, Typography, Icon, notification} from "antd"; -import StarRatings from "react-star-ratings"; -import "./DetailedRating.css"; -import axios from "axios"; -import {withConfigContext} from "../../../context/ConfigContext"; -import {handleApiError} from "../../../js/Utils"; +import React from 'react'; +import { Row, Typography, Icon } from 'antd'; +import StarRatings from 'react-star-ratings'; +import './DetailedRating.css'; +import axios from 'axios'; +import { withConfigContext } from '../../../context/ConfigContext'; +import { handleApiError } from '../../../js/Utils'; const { Text } = Typography; - -class DetailedRating extends React.Component{ - - constructor(props){ - super(props); - this.state={ - detailedRating: null - } - } - - componentDidMount() { - const {type,uuid} = this.props; - this.getData(type,uuid); - } - - componentDidUpdate(prevProps, prevState) { - if (prevProps.uuid !== this.props.uuid) { - const {type,uuid} = this.props; - this.getData(type,uuid); - } - } - - getData = (type, uuid)=>{ - const config = this.props.context; - return axios.get( - window.location.origin+ config.serverConfig.invoker.uri +config.serverConfig.invoker.publisher+"/admin/reviews/"+uuid+"/"+type+"-rating", - ).then(res => { - if (res.status === 200) { - let detailedRating = res.data.data; - this.setState({ - detailedRating - }) - } - - }).catch(function (error) { - handleApiError(error, "Error occurred while trying to load rating for the release.", true); - }); +class DetailedRating extends React.Component { + constructor(props) { + super(props); + this.state = { + detailedRating: null, }; + } - render() { - const detailedRating = this.state.detailedRating; + componentDidMount() { + const { type, uuid } = this.props; + this.getData(type, uuid); + } - - if(detailedRating ==null){ - return null; + componentDidUpdate(prevProps, prevState) { + if (prevProps.uuid !== this.props.uuid) { + const { type, uuid } = this.props; + this.getData(type, uuid); + } + } + + getData = (type, uuid) => { + const config = this.props.context; + return axios + .get( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.publisher + + '/admin/reviews/' + + uuid + + '/' + + type + + '-rating', + ) + .then(res => { + if (res.status === 200) { + let detailedRating = res.data.data; + this.setState({ + detailedRating, + }); } + }) + .catch(function(error) { + handleApiError( + error, + 'Error occurred while trying to load rating for the release.', + true, + ); + }); + }; - const totalCount = detailedRating.noOfUsers; - const ratingVariety = detailedRating.ratingVariety; + render() { + const detailedRating = this.state.detailedRating; - const ratingArray = []; + if (detailedRating == null) { + return null; + } - for (let [key, value] of Object.entries(ratingVariety)) { - ratingArray.push(value); - } + const totalCount = detailedRating.noOfUsers; + const ratingVariety = detailedRating.ratingVariety; - const maximumRating = Math.max(...ratingArray); + const ratingArray = []; - const ratingBarPercentages = [0,0,0,0,0]; + // eslint-disable-next-line no-unused-vars + for (let [key, value] of Object.entries(ratingVariety)) { + ratingArray.push(value); + } - if(maximumRating>0){ - for(let i = 0; i<5; i++){ - ratingBarPercentages[i] = (ratingVariety[(i+1).toString()])/maximumRating*100; - } - } + const maximumRating = Math.max(...ratingArray); + const ratingBarPercentages = [0, 0, 0, 0, 0]; - return ( - -
-
{detailedRating.ratingValue.toFixed(1)}
- -
- {totalCount} total -
-
-
- 5 - -
-
- 4 - -
-
- 3 - -
-
- 2 - -
-
- 1 - -
-
-
- ); + if (maximumRating > 0) { + for (let i = 0; i < 5; i++) { + ratingBarPercentages[i] = + (ratingVariety[(i + 1).toString()] / maximumRating) * 100; + } } -} + return ( + +
+
{detailedRating.ratingValue.toFixed(1)}
+ +
+ + {totalCount} total + +
+
+
+ 5 + + {' '} + +
+
+ 4 + + {' '} + +
+
+ 3 + + {' '} + +
+
+ 2 + + {' '} + +
+
+ 1 + + {' '} + +
+
+
+ ); + } +} -export default withConfigContext(DetailedRating); \ No newline at end of file +export default withConfigContext(DetailedRating); diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js index d9c8f9505d..727a83da55 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/AppDetailsDrawer/AppDetailsDrawer.js @@ -18,700 +18,823 @@ import React from 'react'; import { - Drawer, - Select, - Avatar, - Typography, - Divider, - Tag, - notification, - List, - Button, - Spin, - message, - Icon, - Card, Badge, Tooltip, Popover + Drawer, + Select, + Avatar, + Typography, + Divider, + Tag, + notification, + List, + Button, + Spin, + message, + Icon, + Card, + Badge, + Tooltip, } from 'antd'; -import DetailedRating from "../../detailed-rating/DetailedRating"; -import {Link} from "react-router-dom"; -import axios from "axios"; -import ReactQuill from "react-quill"; +import DetailedRating from '../../detailed-rating/DetailedRating'; +import { Link } from 'react-router-dom'; +import axios from 'axios'; +import ReactQuill from 'react-quill'; import ReactHtmlParser from 'react-html-parser'; -import "./AppDetailsDrawer.css"; -import pSBC from "shade-blend-color"; -import {withConfigContext} from "../../../../context/ConfigContext"; -import ManagedConfigurationsIframe - from "../../../manage/android-enterprise/ManagedConfigurationsIframe/ManagedConfigurationsIframe"; -import {handleApiError} from "../../../../js/Utils"; - -const {Meta} = Card; -const {Text, Title} = Typography; -const {Option} = Select; - -const IconText = ({type, text}) => ( - - - {text} +import './AppDetailsDrawer.css'; +import pSBC from 'shade-blend-color'; +import { withConfigContext } from '../../../../context/ConfigContext'; +import ManagedConfigurationsIframe from '../../../manage/android-enterprise/ManagedConfigurationsIframe/ManagedConfigurationsIframe'; +import { handleApiError } from '../../../../js/Utils'; + +const { Meta } = Card; +const { Text, Title } = Typography; +const { Option } = Select; + +const IconText = ({ type, text }) => ( + + + {text} ); const modules = { - toolbar: [ - ['bold', 'italic', 'underline', 'strike', 'blockquote'], - [{'list': 'ordered'}, {'list': 'bullet'}], - ['link'] - ], + toolbar: [ + ['bold', 'italic', 'underline', 'strike', 'blockquote'], + [{ list: 'ordered' }, { list: 'bullet' }], + ['link'], + ], }; const formats = [ - 'bold', 'italic', 'underline', 'strike', 'blockquote', - 'list', 'bullet', - 'link' + 'bold', + 'italic', + 'underline', + 'strike', + 'blockquote', + 'list', + 'bullet', + 'link', ]; class AppDetailsDrawer extends React.Component { - constructor(props) { - super(props); - const drawerWidth = window.innerWidth <= 770 ? '80%' : '40%'; - - this.state = { - loading: false, - name: "", - description: null, - globalCategories: [], - globalTags: [], - categories: [], - tags: [], - temporaryDescription: null, - temporaryCategories: [], - temporaryTags: [], - isDescriptionEditEnabled: false, - isCategoriesEditEnabled: false, - isTagsEditEnabled: false, - drawer: null, - drawerWidth - }; - } - - componentDidMount() { - this.getCategories(); - } - - componentDidUpdate(prevProps, prevState, snapshot) { - if (prevProps.app !== this.props.app) { - const {name, description, tags, categories} = this.props.app; - this.setState({ - name, - description, - tags, - categories, - isDescriptionEditEnabled: false, - isCategoriesEditEnabled: false, - isTagsEditEnabled: false, - }); - } - } - - getCategories = () => { - const config = this.props.context; - axios.get( - window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/categories" - ).then(res => { - if (res.status === 200) { - const categories = JSON.parse(res.data.data); - this.getTags(); - const globalCategories = categories.map(category => { - return ( - - ) - }); - - this.setState({ - globalCategories, - loading: false - }); - } - - }).catch((error) => { - handleApiError(error, "Error occurred while trying to load categories.", true); - this.setState({ - loading: false - }); - }); - }; - - getTags = () => { - const config = this.props.context; - axios.get( - window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags" - ).then(res => { - if (res.status === 200) { - const tags = JSON.parse(res.data.data); - - const globalTags = tags.map(tag => { - return ( - - ) - }); - - this.setState({ - globalTags, - loading: false - }); - } - - }).catch((error) => { - handleApiError(error, "Error occurred while trying to load tags."); - this.setState({ - loading: false - }); - }); + constructor(props) { + super(props); + const drawerWidth = window.innerWidth <= 770 ? '80%' : '40%'; + + this.state = { + loading: false, + name: '', + description: null, + globalCategories: [], + globalTags: [], + categories: [], + tags: [], + temporaryDescription: null, + temporaryCategories: [], + temporaryTags: [], + isDescriptionEditEnabled: false, + isCategoriesEditEnabled: false, + isTagsEditEnabled: false, + drawer: null, + drawerWidth, }; + } + + componentDidMount() { + this.getCategories(); + } + + componentDidUpdate(prevProps, prevState, snapshot) { + if (prevProps.app !== this.props.app) { + const { name, description, tags, categories } = this.props.app; + this.setState({ + name, + description, + tags, + categories, + isDescriptionEditEnabled: false, + isCategoriesEditEnabled: false, + isTagsEditEnabled: false, + }); + } + } + + getCategories = () => { + const config = this.props.context; + axios + .get( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.publisher + + '/applications/categories', + ) + .then(res => { + if (res.status === 200) { + const categories = JSON.parse(res.data.data); + this.getTags(); + const globalCategories = categories.map(category => { + return ( + + ); + }); - - // change the app name - handleNameSave = name => { - const config = this.props.context; - const {id} = this.props.app; - if (name !== this.state.name && name !== "") { - const data = {name: name}; - axios.put( - window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/" + id, - data - ).then(res => { - if (res.status === 200) { - const app = res.data.data; - this.props.onUpdateApp("name", app.name); - notification["success"]({ - message: 'Saved!', - description: 'App name updated successfully!' - }); - this.setState({ - loading: false, - name: app.name, - }); - - } - }).catch((error) => { - if (error.hasOwnProperty("response") && error.response.status === 401) { - message.error('You are not logged in'); - window.location.href = window.location.origin + '/publisher/login'; - } else { - notification["error"]({ - message: "There was a problem", - duration: 0, - description: - "Error occurred while trying to save the app name.", - }); - } - - this.setState({loading: false}); - }); + this.setState({ + globalCategories, + loading: false, + }); } - }; - - // handle description change - handleDescriptionChange = (temporaryDescription) => { - this.setState({temporaryDescription}) - }; - - enableDescriptionEdit = () => { - this.setState({ - isDescriptionEditEnabled: true, - temporaryDescription: this.state.description - }); - }; - - disableDescriptionEdit = () => { + }) + .catch(error => { + handleApiError( + error, + 'Error occurred while trying to load categories.', + true, + ); this.setState({ - isDescriptionEditEnabled: false, + loading: false, }); - }; - - enableCategoriesEdit = () => { + }); + }; + + getTags = () => { + const config = this.props.context; + axios + .get( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.publisher + + '/applications/tags', + ) + .then(res => { + if (res.status === 200) { + const tags = JSON.parse(res.data.data); + + const globalTags = tags.map(tag => { + return ; + }); + + this.setState({ + globalTags, + loading: false, + }); + } + }) + .catch(error => { + handleApiError(error, 'Error occurred while trying to load tags.'); this.setState({ - isCategoriesEditEnabled: true, - temporaryCategories: this.state.categories + loading: false, }); - }; + }); + }; + + // change the app name + handleNameSave = name => { + const config = this.props.context; + const { id } = this.props.app; + if (name !== this.state.name && name !== '') { + const data = { name: name }; + axios + .put( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.publisher + + '/applications/' + + id, + data, + ) + .then(res => { + if (res.status === 200) { + const app = res.data.data; + this.props.onUpdateApp('name', app.name); + notification.success({ + message: 'Saved!', + description: 'App name updated successfully!', + }); + this.setState({ + loading: false, + name: app.name, + }); + } + }) + .catch(error => { + if ( + error.hasOwnProperty('response') && + error.response.status === 401 + ) { + message.error('You are not logged in'); + window.location.href = window.location.origin + '/publisher/login'; + } else { + notification.error({ + message: 'There was a problem', + duration: 0, + description: 'Error occurred while trying to save the app name.', + }); + } - disableCategoriesEdit = () => { - this.setState({ - isCategoriesEditEnabled: false, + this.setState({ loading: false }); }); - }; - - // handle description change - handleCategoryChange = (temporaryCategories) => { - this.setState({temporaryCategories}) - }; - - // change app categories - handleCategorySave = () => { - const config = this.props.context; - const {id} = this.props.app; - const {temporaryCategories, categories} = this.state; - - const difference = temporaryCategories - .filter(x => !categories.includes(x)) - .concat(categories.filter(x => !temporaryCategories.includes(x))); - - if (difference.length !== 0 && temporaryCategories.length !== 0) { - const data = {categories: temporaryCategories}; - axios.put( - window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/" + id, - data - ).then(res => { - if (res.status === 200) { - const app = res.data.data; - this.props.onUpdateApp("categories", temporaryCategories); - notification["success"]({ - message: 'Saved!', - description: 'App categories updated successfully!' - }); - this.setState({ - loading: false, - categories: app.categories, - isCategoriesEditEnabled: false - }); - - } - }).catch((error) => { - if (error.hasOwnProperty("response") && error.response.status === 401) { - message.error('You are not logged in'); - window.location.href = window.location.origin + '/publisher/login'; - } else { - notification["error"]({ - message: "There was a problem", - duration: 0, - description: - "Error occurred while trying to updating categories.", - }); - } - - this.setState({loading: false}); + } + }; + + // handle description change + handleDescriptionChange = temporaryDescription => { + this.setState({ temporaryDescription }); + }; + + enableDescriptionEdit = () => { + this.setState({ + isDescriptionEditEnabled: true, + temporaryDescription: this.state.description, + }); + }; + + disableDescriptionEdit = () => { + this.setState({ + isDescriptionEditEnabled: false, + }); + }; + + enableCategoriesEdit = () => { + this.setState({ + isCategoriesEditEnabled: true, + temporaryCategories: this.state.categories, + }); + }; + + disableCategoriesEdit = () => { + this.setState({ + isCategoriesEditEnabled: false, + }); + }; + + // handle description change + handleCategoryChange = temporaryCategories => { + this.setState({ temporaryCategories }); + }; + + // change app categories + handleCategorySave = () => { + const config = this.props.context; + const { id } = this.props.app; + const { temporaryCategories, categories } = this.state; + + const difference = temporaryCategories + .filter(x => !categories.includes(x)) + .concat(categories.filter(x => !temporaryCategories.includes(x))); + + if (difference.length !== 0 && temporaryCategories.length !== 0) { + const data = { categories: temporaryCategories }; + axios + .put( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.publisher + + '/applications/' + + id, + data, + ) + .then(res => { + if (res.status === 200) { + const app = res.data.data; + this.props.onUpdateApp('categories', temporaryCategories); + notification.success({ + message: 'Saved!', + description: 'App categories updated successfully!', }); - } - }; + this.setState({ + loading: false, + categories: app.categories, + isCategoriesEditEnabled: false, + }); + } + }) + .catch(error => { + if ( + error.hasOwnProperty('response') && + error.response.status === 401 + ) { + message.error('You are not logged in'); + window.location.href = window.location.origin + '/publisher/login'; + } else { + notification.error({ + message: 'There was a problem', + duration: 0, + description: + 'Error occurred while trying to updating categories.', + }); + } - enableTagsEdit = () => { - this.setState({ - isTagsEditEnabled: true, - temporaryTags: this.state.tags + this.setState({ loading: false }); }); - }; + } + }; + + enableTagsEdit = () => { + this.setState({ + isTagsEditEnabled: true, + temporaryTags: this.state.tags, + }); + }; + + disableTagsEdit = () => { + this.setState({ + isTagsEditEnabled: false, + }); + }; + + // handle description change + handleTagsChange = temporaryTags => { + this.setState({ temporaryTags }); + }; + + // change app tags + handleTagsSave = () => { + const config = this.props.context; + const { id } = this.props.app; + const { temporaryTags, tags } = this.state; + + const difference = temporaryTags + .filter(x => !tags.includes(x)) + .concat(tags.filter(x => !temporaryTags.includes(x))); + + if (difference.length !== 0 && temporaryTags.length !== 0) { + const data = { tags: temporaryTags }; + axios + .put( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.publisher + + '/applications/' + + id, + data, + ) + .then(res => { + if (res.status === 200) { + const app = res.data.data; + notification.success({ + message: 'Saved!', + description: 'App tags updated successfully!', + }); + this.setState({ + loading: false, + tags: app.tags, + isTagsEditEnabled: false, + }); + } + }) + .catch(error => { + if ( + error.hasOwnProperty('response') && + error.response.status === 401 + ) { + message.error('You are not logged in'); + window.location.href = window.location.origin + '/publisher/login'; + } else { + notification.error({ + message: 'There was a problem', + duration: 0, + description: 'Error occurred while trying to update tags', + }); + } - disableTagsEdit = () => { - this.setState({ - isTagsEditEnabled: false, + this.setState({ loading: false }); }); - }; - - // handle description change - handleTagsChange = (temporaryTags) => { - this.setState({temporaryTags}) - }; - - // change app tags - handleTagsSave = () => { - const config = this.props.context; - const {id} = this.props.app; - const {temporaryTags, tags} = this.state; - - - const difference = temporaryTags - .filter(x => !tags.includes(x)) - .concat(tags.filter(x => !temporaryTags.includes(x))); - - if (difference.length !== 0 && temporaryTags.length !== 0) { - const data = {tags: temporaryTags}; - axios.put( - window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/" + id, - data - ).then(res => { - if (res.status === 200) { - const app = res.data.data; - notification["success"]({ - message: 'Saved!', - description: 'App tags updated successfully!' - }); - this.setState({ - loading: false, - tags: app.tags, - isTagsEditEnabled: false - }); - } - }).catch((error) => { - if (error.hasOwnProperty("response") && error.response.status === 401) { - message.error('You are not logged in'); - window.location.href = window.location.origin + '/publisher/login'; - } else { - notification["error"]({ - message: "There was a problem", - duration: 0, - description: - "Error occurred while trying to update tags", - }); - } - - this.setState({loading: false}); + } + }; + + // handle description save + handleDescriptionSave = () => { + const config = this.props.context; + const { id } = this.props.app; + const { description, temporaryDescription } = this.state; + + if ( + temporaryDescription !== description && + temporaryDescription !== '


' + ) { + const data = { description: temporaryDescription }; + axios + .put( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.publisher + + '/applications/' + + id, + data, + ) + .then(res => { + if (res.status === 200) { + const app = res.data.data; + notification.success({ + message: 'Saved!', + description: 'App description updated successfully!', }); - } - }; - - //handle description save - handleDescriptionSave = () => { - const config = this.props.context; - const {id} = this.props.app; - const {description, temporaryDescription} = this.state; - - if (temporaryDescription !== description && temporaryDescription !== "


") { - const data = {description: temporaryDescription}; - axios.put( - window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/" + id, - data - ).then(res => { - if (res.status === 200) { - const app = res.data.data; - notification["success"]({ - message: 'Saved!', - description: 'App description updated successfully!' - }); - this.setState({ - loading: false, - description: app.description, - isDescriptionEditEnabled: false - }); - } - }).catch((error) => { - if (error.hasOwnProperty("response") && error.response.status === 401) { - message.error('You are not logged in'); - window.location.href = window.location.origin + '/publisher/login'; - } else { - message.error('Something went wrong... :('); - } - - this.setState({loading: false}); + this.setState({ + loading: false, + description: app.description, + isDescriptionEditEnabled: false, }); - } else { - this.setState({isDescriptionEditEnabled: false}); - } - }; - - - render() { - const config = this.props.context; - const {app, visible, onClose} = this.props; - const { - name, loading, description, isDescriptionEditEnabled, isCategoriesEditEnabled, - isTagsEditEnabled, temporaryDescription, temporaryCategories, temporaryTags, - globalCategories, globalTags, categories, tags - } = this.state; - if (app == null) { - return null; - } + } + }) + .catch(error => { + if ( + error.hasOwnProperty('response') && + error.response.status === 401 + ) { + message.error('You are not logged in'); + window.location.href = window.location.origin + '/publisher/login'; + } else { + message.error('Something went wrong... :('); + } + + this.setState({ loading: false }); + }); + } else { + this.setState({ isDescriptionEditEnabled: false }); + } + }; + + render() { + const config = this.props.context; + const { app, visible, onClose } = this.props; + const { + name, + loading, + description, + isDescriptionEditEnabled, + isCategoriesEditEnabled, + isTagsEditEnabled, + temporaryDescription, + temporaryCategories, + temporaryTags, + globalCategories, + globalTags, + categories, + tags, + } = this.state; + if (app == null) { + return null; + } - let avatar = null; - - if (app.applicationReleases.length === 0) { - const avatarLetter = name.charAt(0).toUpperCase(); - avatar = ( - - {avatarLetter} - - ); - } else { - avatar = ( - - ) - } + let avatar = null; + + if (app.applicationReleases.length === 0) { + const avatarLetter = name.charAt(0).toUpperCase(); + avatar = ( + + {avatarLetter} + + ); + } else { + avatar = ( + + ); + } - return ( -
- - -
- {avatar} - {name} -
- - {/*display manage config button only if the app is public android app*/} - {(app.isAndroidEnterpriseApp) && - (config.androidEnterpriseToken !== null) && - ( -
+ return ( +
+ + +
+ {avatar} + + {name} + +
+ + {/* display manage config button only if the app is public android app*/} + {app.isAndroidEnterpriseApp && + config.androidEnterpriseToken !== null && ( +
+
+ Set up managed configurations +
+
+ + If you are developing apps for the enterprise market, you + may need to satisfy particular requirements set by a + organization's policies. Managed configurations, + previously known as application restrictions, allow the + organization's IT admin to remotely specify settings + for apps. This capability is particularly useful for + organization-approved apps deployed to a work profile. + +
+
+ + +
+ )} + Releases +
+ ( +
+ + + + + - Set up managed configurations -
-
- - If you are developing apps for the enterprise market, you may need to satisfy - particular requirements set by a organization's policies. Managed - configurations, - previously known as application restrictions, allow the organization's IT admin - to - remotely specify settings for apps. This capability is particularly useful for - organization-approved apps deployed to a work profile. - + {release.currentStatus === 'PUBLISHED' ? ( + + + + } + > + + + ) : ( + + )}
-
- - -
- )} - Releases -
- ( -
- - - - - - {(release.currentStatus === "PUBLISHED") ? ( - - - - }> - - - ) : ( - - )} -
- } - title={release.version} - description={ -
- - - - - -
- } - /> - - - - -
- )} - /> -
- - {/*display add new release only if app type is enterprise*/} - {(app.type === "ENTERPRISE") && ( -
- -
- - Add new release for the application - -
- - - -
)} - - - Description - {!isDescriptionEditEnabled && ( - - - - )} - - {!isDescriptionEditEnabled && ( -
{ReactHtmlParser(description)}
- )} - - {isDescriptionEditEnabled && ( -
- - - -
- )} - - - Categories - {!isCategoriesEditEnabled && ( - - - )} -
-
- {isCategoriesEditEnabled && ( -
- -
- - -
-
- )} - {!isCategoriesEditEnabled && ( - { - categories.map(category => { - return ( - - {category} - - ); - }) - } - )} - - - - Tags - {!isTagsEditEnabled && ( - - - )} -
-
- {isTagsEditEnabled && ( -
- -
- - + + + + +
-
- )} - {!isTagsEditEnabled && ( - { - tags.map(tag => { - return ( - - {tag} - - ); - }) - } - )} - - - -
- {app.applicationReleases.length > 0 && ( - )} -
- - + } + /> + + + + +
+ )} + />
- ); - } + + {/* display add new release only if app type is enterprise*/} + {app.type === 'ENTERPRISE' && ( +
+ +
+ Add new release for the application +
+ + + +
+ )} + + + Description + {!isDescriptionEditEnabled && ( + + + + )} + + {!isDescriptionEditEnabled && ( +
{ReactHtmlParser(description)}
+ )} + + {isDescriptionEditEnabled && ( +
+ + + +
+ )} + + + Categories + {!isCategoriesEditEnabled && ( + + + + )} +
+
+ {isCategoriesEditEnabled && ( +
+ +
+ + +
+
+ )} + {!isCategoriesEditEnabled && ( + + {categories.map(category => { + return ( + + {category} + + ); + })} + + )} + + + Tags + {!isTagsEditEnabled && ( + + + + )} +
+
+ {isTagsEditEnabled && ( +
+ +
+ + +
+
+ )} + {!isTagsEditEnabled && ( + + {tags.map(tag => { + return ( + + {tag} + + ); + })} + + )} + + + +
+ {app.applicationReleases.length > 0 && ( + + )} +
+ + + + ); + } } export default withConfigContext(AppDetailsDrawer); diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/Filters.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/Filters.js index 14c76e24a7..a3f2d7c467 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/Filters.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/Filters.js @@ -16,314 +16,327 @@ * under the License. */ -import React from "react"; +import React from 'react'; import { - Card, - Col, - Row, - Typography, - Input, - Divider, - Icon, - Select, - Button, - Form, - message, - Radio, - notification, Alert -} from "antd"; -import axios from "axios"; -import {withConfigContext} from "../../../context/ConfigContext"; -import {handleApiError} from "../../../js/Utils"; - -const {Option} = Select; -const {Title} = Typography; + Card, + Col, + Row, + Typography, + Divider, + Select, + Button, + Form, + Alert, +} from 'antd'; +import axios from 'axios'; +import { withConfigContext } from '../../../context/ConfigContext'; +import { handleApiError } from '../../../js/Utils'; +const { Option } = Select; +const { Title } = Typography; class FiltersForm extends React.Component { - constructor(props) { - super(props); - this.state = { - categories: [], - tags: [], - deviceTypes: [], - forbiddenErrors: { - categories: false, - tags: false, - deviceTypes: false - } - }; - } - - handleSubmit = e => { - e.preventDefault(); - this.props.form.validateFields((err, values) => { - for (const [key, value] of Object.entries(values)) { - if (value === undefined) { - delete values[key]; - } - } - - if (values.hasOwnProperty("deviceType") && values.deviceType === "ALL") { - delete values["deviceType"]; - } - - if (values.hasOwnProperty("appType") && values.appType === "ALL") { - delete values["appType"]; - } - - this.props.setFilters(values); - }); + constructor(props) { + super(props); + this.state = { + categories: [], + tags: [], + deviceTypes: [], + forbiddenErrors: { + categories: false, + tags: false, + deviceTypes: false, + }, }; + } - componentDidMount() { - this.getCategories(); - this.getTags(); - this.getDeviceTypes(); - } - - getCategories = () => { - const config = this.props.context; - axios.get( - window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/categories" - ).then(res => { - if (res.status === 200) { - let categories = JSON.parse(res.data.data); - this.setState({ - categories: categories, - loading: false - }); - } + handleSubmit = e => { + e.preventDefault(); + this.props.form.validateFields((err, values) => { + for (const [key, value] of Object.entries(values)) { + if (value === undefined) { + delete values[key]; + } + } - }).catch((error) => { - handleApiError(error, "Error occurred while trying to load categories.", true); - if (error.hasOwnProperty("response") && error.response.status === 403) { - const {forbiddenErrors} = this.state; - forbiddenErrors.categories = true; - this.setState({ - forbiddenErrors, - loading: false - }) - } else { - this.setState({ - loading: false - }); - } - }); - }; + if (values.hasOwnProperty('deviceType') && values.deviceType === 'ALL') { + delete values.deviceType; + } - getTags = () => { - const config = this.props.context; - axios.get( - window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications/tags" - ).then(res => { - if (res.status === 200) { - let tags = JSON.parse(res.data.data); - this.setState({ - tags: tags, - loading: false, - }); - } + if (values.hasOwnProperty('appType') && values.appType === 'ALL') { + delete values.appType; + } - }).catch((error) => { - handleApiError(error, "Error occurred while trying to load tags.", true); - if (error.hasOwnProperty("response") && error.response.status === 403) { - const {forbiddenErrors} = this.state; - forbiddenErrors.tags = true; - this.setState({ - forbiddenErrors, - loading: false - }) - } else { - this.setState({ - loading: false - }); - } - }); - }; + this.props.setFilters(values); + }); + }; + componentDidMount() { + this.getCategories(); + this.getTags(); + this.getDeviceTypes(); + } - getDeviceTypes = () => { - const config = this.props.context; - axios.get( - window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.deviceMgt + "/device-types" - ).then(res => { - if (res.status === 200) { - const deviceTypes = JSON.parse(res.data.data); - this.setState({ - deviceTypes, - loading: false, - }); - } + getCategories = () => { + const config = this.props.context; + axios + .get( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.publisher + + '/applications/categories', + ) + .then(res => { + if (res.status === 200) { + let categories = JSON.parse(res.data.data); + this.setState({ + categories: categories, + loading: false, + }); + } + }) + .catch(error => { + handleApiError( + error, + 'Error occurred while trying to load categories.', + true, + ); + if (error.hasOwnProperty('response') && error.response.status === 403) { + const { forbiddenErrors } = this.state; + forbiddenErrors.categories = true; + this.setState({ + forbiddenErrors, + loading: false, + }); + } else { + this.setState({ + loading: false, + }); + } + }); + }; - }).catch((error) => { - handleApiError(error, "Error occurred while trying to load device types.", true); - if (error.hasOwnProperty("response") && error.response.status === 403) { - const {forbiddenErrors} = this.state; - forbiddenErrors.deviceTypes = true; - this.setState({ - forbiddenErrors, - loading: false - }) - } else { - this.setState({ - loading: false - }); - } - }); - }; + getTags = () => { + const config = this.props.context; + axios + .get( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.publisher + + '/applications/tags', + ) + .then(res => { + if (res.status === 200) { + let tags = JSON.parse(res.data.data); + this.setState({ + tags: tags, + loading: false, + }); + } + }) + .catch(error => { + handleApiError( + error, + 'Error occurred while trying to load tags.', + true, + ); + if (error.hasOwnProperty('response') && error.response.status === 403) { + const { forbiddenErrors } = this.state; + forbiddenErrors.tags = true; + this.setState({ + forbiddenErrors, + loading: false, + }); + } else { + this.setState({ + loading: false, + }); + } + }); + }; - render() { - const {categories, tags, deviceTypes, forbiddenErrors} = this.state; - const {getFieldDecorator} = this.props.form; + getDeviceTypes = () => { + const config = this.props.context; + axios + .get( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.deviceMgt + + '/device-types', + ) + .then(res => { + if (res.status === 200) { + const deviceTypes = JSON.parse(res.data.data); + this.setState({ + deviceTypes, + loading: false, + }); + } + }) + .catch(error => { + handleApiError( + error, + 'Error occurred while trying to load device types.', + true, + ); + if (error.hasOwnProperty('response') && error.response.status === 403) { + const { forbiddenErrors } = this.state; + forbiddenErrors.deviceTypes = true; + this.setState({ + forbiddenErrors, + loading: false, + }); + } else { + this.setState({ + loading: false, + }); + } + }); + }; - return ( + render() { + const { categories, tags, deviceTypes, forbiddenErrors } = this.state; + const { getFieldDecorator } = this.props.form; - -
- - - Filter - - - - - - - - {(forbiddenErrors.categories) && ( - - )} - - {getFieldDecorator('categories', { - rules: [{ - required: false, - message: 'Please select categories' - }], - })( - - )} - + return ( + + + + + Filter + + + + + + + + {forbiddenErrors.categories && ( + + )} + + {getFieldDecorator('categories', { + rules: [ + { + required: false, + message: 'Please select categories', + }, + ], + })( + , + )} + - {(forbiddenErrors.deviceTypes) && ( - - )} - - {getFieldDecorator('deviceType', { - rules: [{ - required: false, - message: 'Please select device types' - }], - })( - - )} - - {(forbiddenErrors.tags) && ( - - )} - - {getFieldDecorator('tags', { - rules: [{ - required: false, - message: 'Please select tags' - }], - })( - - )} - + {forbiddenErrors.deviceTypes && ( + + )} + + {getFieldDecorator('deviceType', { + rules: [ + { + required: false, + message: 'Please select device types', + }, + ], + })( + , + )} + + {forbiddenErrors.tags && ( + + )} + + {getFieldDecorator('tags', { + rules: [ + { + required: false, + message: 'Please select tags', + }, + ], + })( + , + )} + - - {getFieldDecorator('appType', {})( - - )} - - - - - ); - } + + {getFieldDecorator('appType', {})( + , + )} + + + +
+ ); + } } +const Filters = withConfigContext( + Form.create({ name: 'filter-apps' })(FiltersForm), +); -const Filters = withConfigContext(Form.create({name: 'filter-apps'})(FiltersForm)); - - -export default withConfigContext(Filters); \ No newline at end of file +export default withConfigContext(Filters); diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/ListApps.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/ListApps.js index 2af6f06975..411012bc9d 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/ListApps.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/ListApps.js @@ -16,89 +16,87 @@ * under the License. */ -import React from "react"; -import {Card, Col, Row, Typography, Input, Divider, notification} from "antd"; -import AppsTable from "./appsTable/AppsTable"; -import Filters from "./Filters"; -import AppDetailsDrawer from "./AppDetailsDrawer/AppDetailsDrawer"; -import axios from "axios"; +import React from 'react'; +import { Card, Col, Row, Typography, Input, Divider } from 'antd'; +import AppsTable from './appsTable/AppsTable'; +import Filters from './Filters'; -const {Title} = Typography; +const { Title } = Typography; const Search = Input.Search; class ListApps extends React.Component { - constructor(props) { - super(props); - this.state = { - filters: {} - }; - this.appName = ''; - } - - setFilters = (filters) => { - if (this.appName === '' && filters.hasOwnProperty("appName")) { - delete filters["appName"]; - } else { - filters.appName = this.appName; - } - this.setState({ - filters - }); + constructor(props) { + super(props); + this.state = { + filters: {}, }; + this.appName = ''; + } - setSearchText = (appName) => { - const filters = {...this.state.filters}; - this.appName = appName; - if (appName === '' && filters.hasOwnProperty("appName")) { - delete filters["appName"]; - } else { - filters.appName = appName; - } - this.setState({ - filters - }); - }; + setFilters = filters => { + if (this.appName === '' && filters.hasOwnProperty('appName')) { + delete filters.appName; + } else { + filters.appName = this.appName; + } + this.setState({ + filters, + }); + }; - onChangeSearchText = (e) => { - const filters = {...this.state.filters}; - const appName = e.target.value; - if (appName === '' && filters.hasOwnProperty("appName")) { - delete filters["appName"]; - this.setState({ - filters - }); - } - }; + setSearchText = appName => { + const filters = { ...this.state.filters }; + this.appName = appName; + if (appName === '' && filters.hasOwnProperty('appName')) { + delete filters.appName; + } else { + filters.appName = appName; + } + this.setState({ + filters, + }); + }; - render() { - const {isDrawerVisible, filters} = this.state; - return ( - - - - - - - - - Apps - - - - - - - - - - - ); + onChangeSearchText = e => { + const filters = { ...this.state.filters }; + const appName = e.target.value; + if (appName === '' && filters.hasOwnProperty('appName')) { + delete filters.appName; + this.setState({ + filters, + }); } + }; + + render() { + const { filters } = this.state; + return ( + + + + + + + + + Apps + + + + + + + + + + + ); + } } export default ListApps; diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js index 6158df2f2a..50149ef8bb 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/list-apps/appsTable/AppsTable.js @@ -16,292 +16,321 @@ * under the License. */ -import React from "react"; -import {Avatar, Table, Tag, Icon, message, notification, Col, Badge, Alert, Tooltip} from "antd"; -import axios from "axios"; +import React from 'react'; +import { Avatar, Table, Tag, Icon, Badge, Alert, Tooltip } from 'antd'; +import axios from 'axios'; import pSBC from 'shade-blend-color'; -import "./AppsTable.css"; -import {withConfigContext} from "../../../../context/ConfigContext"; -import AppDetailsDrawer from "../AppDetailsDrawer/AppDetailsDrawer"; -import {handleApiError} from "../../../../js/Utils"; +import './AppsTable.css'; +import { withConfigContext } from '../../../../context/ConfigContext'; +import AppDetailsDrawer from '../AppDetailsDrawer/AppDetailsDrawer'; +import { handleApiError } from '../../../../js/Utils'; let config = null; const columns = [ - { - title: '', - dataIndex: 'name', - render: (name, row) => { - let avatar = null; - if (row.applicationReleases.length === 0) { - const avatarLetter = name.charAt(0).toUpperCase(); - avatar = ( - - {avatarLetter} - - ); - } else { - const {applicationReleases} = row; - let hasPublishedRelease = false; - for (let i = 0; i < applicationReleases.length; i++) { - if (applicationReleases[i].currentStatus === "PUBLISHED") { - hasPublishedRelease = true; - break; - } - } - avatar = (hasPublishedRelease) ? ( - - - - }> - - - ) : ( - - ); - } - - return ( -
- {avatar} - {name} -
); + { + title: '', + dataIndex: 'name', + // eslint-disable-next-line react/display-name + render: (name, row) => { + let avatar = null; + if (row.applicationReleases.length === 0) { + const avatarLetter = name.charAt(0).toUpperCase(); + avatar = ( + + {avatarLetter} + + ); + } else { + const { applicationReleases } = row; + let hasPublishedRelease = false; + for (let i = 0; i < applicationReleases.length; i++) { + if (applicationReleases[i].currentStatus === 'PUBLISHED') { + hasPublishedRelease = true; + break; + } } - }, - { - title: 'Categories', - dataIndex: 'categories', - render: categories => ( - - {categories.map(category => { - return ( - - {category} - - ); - })} - - ) - }, - { - title: 'Platform', - dataIndex: 'deviceType', - render: platform => { - const defaultPlatformIcons = config.defaultPlatformIcons; - let icon = defaultPlatformIcons.default.icon; - let color = defaultPlatformIcons.default.color; - let theme = defaultPlatformIcons.default.theme; - if (defaultPlatformIcons.hasOwnProperty(platform)) { - icon = defaultPlatformIcons[platform].icon; - color = defaultPlatformIcons[platform].color; - theme = defaultPlatformIcons[platform].theme; + avatar = hasPublishedRelease ? ( + + + } - return ( - - - - ); - } - }, - { - title: 'Type', - dataIndex: 'type' + > + + + ) : ( + + ); + } + + return ( +
+ {avatar} + {name} +
+ ); }, - { - title: 'Subscription', - dataIndex: 'subMethod' + }, + { + title: 'Categories', + dataIndex: 'categories', + // eslint-disable-next-line react/display-name + render: categories => ( + + {categories.map(category => { + return ( + + {category} + + ); + })} + + ), + }, + { + title: 'Platform', + dataIndex: 'deviceType', + // eslint-disable-next-line react/display-name + render: platform => { + const defaultPlatformIcons = config.defaultPlatformIcons; + let icon = defaultPlatformIcons.default.icon; + let color = defaultPlatformIcons.default.color; + let theme = defaultPlatformIcons.default.theme; + if (defaultPlatformIcons.hasOwnProperty(platform)) { + icon = defaultPlatformIcons[platform].icon; + color = defaultPlatformIcons[platform].color; + theme = defaultPlatformIcons[platform].theme; + } + return ( + + + + ); }, + }, + { + title: 'Type', + dataIndex: 'type', + }, + { + title: 'Subscription', + dataIndex: 'subMethod', + }, ]; class AppsTable extends React.Component { - constructor(props) { - super(props); - this.state = { - pagination: {}, - apps: [], - filters: {}, - isDrawerVisible: false, - selectedApp: null, - selectedAppIndex: -1, - loading: false, - isForbiddenErrorVisible: false - }; - config = this.props.context; - } + constructor(props) { + super(props); + this.state = { + pagination: {}, + apps: [], + filters: {}, + isDrawerVisible: false, + selectedApp: null, + selectedAppIndex: -1, + loading: false, + isForbiddenErrorVisible: false, + }; + config = this.props.context; + } - componentDidMount() { - const {filters} = this.props; - this.setState({ - filters - }); - this.fetch(filters); + componentDidMount() { + const { filters } = this.props; + this.setState({ + filters, + }); + this.fetch(filters); + } + componentDidUpdate(prevProps, prevState, snapshot) { + const { filters } = this.props; + if (prevProps.filters !== this.props.filters) { + this.setState({ + filters, + }); + this.fetch(filters); } + } - componentDidUpdate(prevProps, prevState, snapshot) { - const {filters} = this.props; - if (prevProps.filters !== this.props.filters) { - this.setState({ - filters - }); - this.fetch(filters); - } - } + // handler to show app drawer + showDrawer = (app, appIndex) => { + this.setState({ + isDrawerVisible: true, + selectedApp: app, + selectedAppIndex: appIndex, + }); + }; - //handler to show app drawer - showDrawer = (app, appIndex) => { - this.setState({ - isDrawerVisible: true, - selectedApp: app, - selectedAppIndex: appIndex - }); - }; + // handler to close the app drawer + closeDrawer = () => { + this.setState({ + isDrawerVisible: false, + }); + }; - // handler to close the app drawer - closeDrawer = () => { - this.setState({ - isDrawerVisible: false - }) - }; + handleTableChange = (pagination, filters, sorter) => { + const pager = { ...this.state.pagination }; + pager.current = pagination.current; - handleTableChange = (pagination, filters, sorter) => { - const pager = {...this.state.pagination}; - pager.current = pagination.current; + this.setState({ + pagination: pager, + }); + this.fetch(this.state.filters, { + results: pagination.pageSize, + page: pagination.current, + sortField: sorter.field, + sortOrder: sorter.order, + ...filters, + }); + }; - this.setState({ - pagination: pager, - }); - this.fetch(this.state.filters, { - results: pagination.pageSize, - page: pagination.current, - sortField: sorter.field, - sortOrder: sorter.order, - ...filters, - }); - }; - - fetch = (filters, params = {}) => { - this.setState({loading: true}); - const config = this.props.context; - - if (!params.hasOwnProperty("page")) { - params.page = 1; - } + fetch = (filters, params = {}) => { + this.setState({ loading: true }); + const config = this.props.context; - const data = { - offset: 10 * (params.page - 1), - limit: 10, - ...filters - }; - - axios.post( - window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications", - data, - ).then(res => { - if (res.status === 200) { - const data = res.data.data; - let apps = []; + if (!params.hasOwnProperty('page')) { + params.page = 1; + } - if (res.data.data.hasOwnProperty("applications")) { - apps = data.applications; - } - const pagination = {...this.state.pagination}; - // Read total count from server - // pagination.total = data.totalCount; - pagination.total = data.pagination.count; - this.setState({ - loading: false, - apps: apps, - pagination, - }); - } - }).catch((error) => { - handleApiError(error, "Error occurred while trying to load apps.", true); - if (error.hasOwnProperty("response") && error.response.status === 403) { - this.setState({ - isForbiddenErrorVisible: true - }) - } - this.setState({loading: false}); - }); + const data = { + offset: 10 * (params.page - 1), + limit: 10, + ...filters, }; - onUpdateApp = (key, value) => { - const apps = [...this.state.apps]; - apps[this.state.selectedAppIndex][key] = value; - this.setState({ - apps - }); - }; + axios + .post( + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.publisher + + '/applications', + data, + ) + .then(res => { + if (res.status === 200) { + const data = res.data.data; + let apps = []; - render() { - const {isDrawerVisible, loading} = this.state; - return ( -
- {(this.state.isForbiddenErrorVisible) && ( - - )} -
- record.id} - dataSource={this.state.apps} - columns={columns} - pagination={this.state.pagination} - onChange={this.handleTableChange} - rowClassName="app-row" - loading={loading} - onRow={(record, rowIndex) => { - return { - onClick: event => { - this.showDrawer(record, rowIndex); - }, - }; - }}/> - - - + if (res.data.data.hasOwnProperty('applications')) { + apps = data.applications; + } + const pagination = { ...this.state.pagination }; + // Read total count from server + // pagination.total = data.totalCount; + pagination.total = data.pagination.count; + this.setState({ + loading: false, + apps: apps, + pagination, + }); + } + }) + .catch(error => { + handleApiError( + error, + 'Error occurred while trying to load apps.', + true, ); - } + if (error.hasOwnProperty('response') && error.response.status === 403) { + this.setState({ + isForbiddenErrorVisible: true, + }); + } + this.setState({ loading: false }); + }); + }; + + onUpdateApp = (key, value) => { + const apps = [...this.state.apps]; + apps[this.state.selectedAppIndex][key] = value; + this.setState({ + apps, + }); + }; + + render() { + const { isDrawerVisible, loading } = this.state; + return ( +
+ {this.state.isForbiddenErrorVisible && ( + + )} +
+
record.id} + dataSource={this.state.apps} + columns={columns} + pagination={this.state.pagination} + onChange={this.handleTableChange} + rowClassName="app-row" + loading={loading} + onRow={(record, rowIndex) => { + return { + onClick: event => { + this.showDrawer(record, rowIndex); + }, + }; + }} + /> + + + + ); + } } -export default withConfigContext(AppsTable); \ No newline at end of file +export default withConfigContext(AppsTable); diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/ReleaseView.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/ReleaseView.js index 6ea67eee20..fd793ecdbc 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/ReleaseView.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/ReleaseView.js @@ -16,158 +16,173 @@ * under the License. */ -import React from "react"; -import {Divider, Row, Col, Typography, Button, Drawer, Icon, Tooltip, Empty} from "antd"; -import StarRatings from "react-star-ratings"; -import Reviews from "./review/Reviews"; -import "../../../App.css"; -import DetailedRating from "../detailed-rating/DetailedRating"; -import EditRelease from "./edit-release/EditRelease"; -import {withConfigContext} from "../../../context/ConfigContext"; -import NewAppUploadForm from "../../new-app/subForms/NewAppUploadForm"; +import React from 'react'; +import { Divider, Row, Col, Typography, Button, Icon, Tooltip } from 'antd'; +import StarRatings from 'react-star-ratings'; +import Reviews from './review/Reviews'; +import '../../../App.css'; +import DetailedRating from '../detailed-rating/DetailedRating'; +import EditRelease from './edit-release/EditRelease'; +import { withConfigContext } from '../../../context/ConfigContext'; -const {Title, Text, Paragraph} = Typography; +const { Title, Text, Paragraph } = Typography; class ReleaseView extends React.Component { - constructor(props) { - super(props); - this.state = { + constructor(props) { + super(props); + this.state = {}; + } - } - } - - componentDidMount() { - console.log("mounted: Release view"); - } - - render() { - const {app, release} = this.props; - const config = this.props.context; - const {lifecycle, currentLifecycleStatus} = this.props; - - if (release == null || lifecycle == null) { - return null; - } + componentDidMount() { + console.log('mounted: Release view'); + } - const {isAppUpdatable, isAppInstallable} = lifecycle[currentLifecycleStatus]; + render() { + const { app, release } = this.props; + const config = this.props.context; + const { lifecycle, currentLifecycleStatus } = this.props; - const platform = app.deviceType; - const defaultPlatformIcons = config.defaultPlatformIcons; - let icon = defaultPlatformIcons.default.icon; - let color = defaultPlatformIcons.default.color; - let theme = defaultPlatformIcons.default.theme; + if (release == null || lifecycle == null) { + return null; + } - if (defaultPlatformIcons.hasOwnProperty(platform)) { - icon = defaultPlatformIcons[platform].icon; - color = defaultPlatformIcons[platform].color; - theme = defaultPlatformIcons[platform].theme; - } - let metaData = []; - try{ - metaData = JSON.parse(release.metaData); - }catch (e) { + const { isAppUpdatable, isAppInstallable } = lifecycle[ + currentLifecycleStatus + ]; - } + const platform = app.deviceType; + const defaultPlatformIcons = config.defaultPlatformIcons; + let icon = defaultPlatformIcons.default.icon; + let color = defaultPlatformIcons.default.color; + let theme = defaultPlatformIcons.default.theme; - return ( -
-
- -
- icon - - - {app.name} - -
- Platform : - - - - - Version : {release.version}
+ if (defaultPlatformIcons.hasOwnProperty(platform)) { + icon = defaultPlatformIcons[platform].icon; + color = defaultPlatformIcons[platform].color; + theme = defaultPlatformIcons[platform].theme; + } + let metaData = []; + try { + metaData = JSON.parse(release.metaData); + } catch (e) { + console.log(e); + } - + return ( +
+
+ +
+ icon + + + {app.name} + +
+ Platform : + + + + + Version : {release.version} +
- -
-
- - - -
- - - - - {release.screenshots.map((screenshotUrl, index) => { - return ( -
- -
- ) - })} -
- - - {release.description} - - - META DATA - - { - metaData.map((data, index)=>{ - return ( -
- {data.key}
- {data.value} - - ) - }) - } - {(metaData.length===0) && (No meta data available.)} - - - REVIEWS - - - - - - + + + +
+ + + +
+ + + + + {release.screenshots.map((screenshotUrl, index) => { + return ( +
+
- - ); - } + ); + })} +
+ + + {release.description} + + + META DATA + + {metaData.map((data, index) => { + return ( +
+ {data.key} +
+ {data.value} + + ); + })} + {metaData.length === 0 && ( + No meta data available. + )} + + + REVIEWS + + + + + + + + + ); + } } export default withConfigContext(ReleaseView); diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/edit-release/EditRelease.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/edit-release/EditRelease.js index f4c6e84670..e40b3b3c3c 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/edit-release/EditRelease.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/edit-release/EditRelease.js @@ -16,663 +16,720 @@ * under the License. */ -import React from "react"; +import React from 'react'; import { - Modal, - Button, - Icon, - notification, - Spin, - Tooltip, - Upload, - Input, - Switch, - Form, - Divider, - Row, - Col, - Select, Alert + Modal, + Button, + Icon, + notification, + Spin, + Tooltip, + Upload, + Input, + Form, + Divider, + Row, + Col, + Select, + Alert, } from 'antd'; -import axios from "axios"; -import "@babel/polyfill"; -import {withConfigContext} from "../../../../context/ConfigContext"; +import axios from 'axios'; +import '@babel/polyfill'; +import { withConfigContext } from '../../../../context/ConfigContext'; -const {TextArea} = Input; +const { TextArea } = Input; const InputGroup = Input.Group; -const {Option} = Select; +const { Option } = Select; const formItemLayout = { - labelCol: { - span: 8, - }, - wrapperCol: { - span: 16, - }, + labelCol: { + span: 8, + }, + wrapperCol: { + span: 16, + }, }; function getBase64(file) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => resolve(reader.result); - reader.onerror = error => reject(error); - }); + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => resolve(reader.result); + reader.onerror = error => reject(error); + }); } class EditReleaseModal extends React.Component { - // To add subscription type & tenancy sharing, refer https://gitlab.com/entgra/carbon-device-mgt/merge_requests/331 - constructor(props) { - super(props); - this.state = { - visible: false, - current: 0, - categories: [], - tags: [], - icons: [], - screenshots: [], - loading: false, - binaryFiles: [], - metaData: [], - formConfig: { - specificElements: {} - } + // To add subscription type & tenancy sharing, refer https://gitlab.com/entgra/carbon-device-mgt/merge_requests/331 + constructor(props) { + super(props); + this.state = { + visible: false, + current: 0, + categories: [], + tags: [], + icons: [], + screenshots: [], + loading: false, + binaryFiles: [], + metaData: [], + formConfig: { + specificElements: {}, + }, + }; + this.lowerOsVersion = null; + this.upperOsVersion = null; + } + + componentDidMount = () => { + this.generateConfig(); + }; + + generateConfig = () => { + const { type } = this.props; + const formConfig = { + type, + }; + + switch (type) { + case 'ENTERPRISE': + formConfig.endpoint = '/ent-app-release'; + formConfig.specificElements = { + binaryFile: { + required: true, + }, + }; + break; + case 'PUBLIC': + formConfig.endpoint = '/public-app-release'; + formConfig.specificElements = { + packageName: { + required: true, + }, + version: { + required: true, + }, }; - this.lowerOsVersion = null; - this.upperOsVersion = null; + break; + case 'WEB_CLIP': + formConfig.endpoint = '/web-app-release'; + formConfig.specificElements = { + version: { + required: true, + }, + url: { + required: true, + }, + }; + break; + case 'CUSTOM': + formConfig.endpoint = '/custom-app-release'; + formConfig.specificElements = { + binaryFile: { + required: true, + }, + packageName: { + required: true, + }, + version: { + required: true, + }, + }; + break; } - componentDidMount = () => { - this.generateConfig(); - }; + this.setState({ + formConfig, + }); + }; + + showModal = () => { + const config = this.props.context; + const { release } = this.props; + const { formConfig } = this.state; + const { specificElements } = formConfig; + let metaData = []; + + try { + metaData = JSON.parse(release.metaData); + } catch (e) { + console.log(e); + } - generateConfig = () => { - const {type} = this.props; - const formConfig = { - type - }; + this.props.form.setFields({ + releaseType: { + value: release.releaseType, + }, + releaseDescription: { + value: release.description, + }, + }); - switch (type) { - case "ENTERPRISE": - formConfig.endpoint = "/ent-app-release"; - formConfig.specificElements = { - binaryFile: { - required: true - } - }; - break; - case "PUBLIC": - formConfig.endpoint = "/public-app-release"; - formConfig.specificElements = { - packageName: { - required: true - }, - version: { - required: true - } - }; - break; - case "WEB_CLIP": - formConfig.endpoint = "/web-app-release"; - formConfig.specificElements = { - version: { - required: true - }, - url: { - required: true - } - }; - break; - case "CUSTOM": - formConfig.endpoint = "/custom-app-release"; - formConfig.specificElements = { - binaryFile: { - required: true - }, - packageName: { - required: true - }, - version: { - required: true - } - }; - break; - } + if (config.deviceTypes.mobileTypes.includes(this.props.deviceType)) { + const osVersions = release.supportedOsVersions.split('-'); + this.lowerOsVersion = osVersions[0]; + this.upperOsVersion = osVersions[1]; + this.props.form.setFields({ + lowerOsVersion: { + value: osVersions[0], + }, + upperOsVersion: { + value: osVersions[1], + }, + }); + } + if (specificElements.hasOwnProperty('version')) { + this.props.form.setFields({ + version: { + value: release.version, + }, + }); + } - this.setState({ - formConfig - }); - }; + if (specificElements.hasOwnProperty('url')) { + this.props.form.setFields({ + url: { + value: release.url, + }, + }); + } + if (specificElements.hasOwnProperty('packageName')) { + this.props.form.setFields({ + packageName: { + value: release.packageName, + }, + }); + } - showModal = () => { - const config = this.props.context; - const {app, release} = this.props; - const {formConfig} = this.state; - const {specificElements} = formConfig; - let metaData = []; + this.setState({ + visible: true, + metaData, + }); + }; - try { - metaData = JSON.parse(release.metaData); - } catch (e) { + handleOk = e => { + this.setState({ + visible: false, + }); + }; - } + handleCancel = e => { + this.setState({ + visible: false, + }); + }; - this.props.form.setFields({ - releaseType: { - value: release.releaseType - }, - releaseDescription: { - value: release.description - } - }); + normFile = e => { + if (Array.isArray(e)) { + return e; + } + return e && e.fileList; + }; - if ((config.deviceTypes.mobileTypes.includes(this.props.deviceType))) { - const osVersions = release.supportedOsVersions.split("-"); - this.lowerOsVersion = osVersions[0]; - this.upperOsVersion = osVersions[1]; - this.props.form.setFields({ - lowerOsVersion: { - value: osVersions[0] - }, - upperOsVersion: { - value: osVersions[1] - } - }); - } - if (specificElements.hasOwnProperty("version")) { - this.props.form.setFields({ - version: { - value: release.version - } - }); - } + handleIconChange = ({ fileList }) => this.setState({ icons: fileList }); + handleBinaryFileChange = ({ fileList }) => + this.setState({ binaryFiles: fileList }); - if (specificElements.hasOwnProperty("url")) { - this.props.form.setFields({ - url: { - value: release.url - } - }); - } + handleScreenshotChange = ({ fileList }) => + this.setState({ screenshots: fileList }); - if (specificElements.hasOwnProperty("packageName")) { - this.props.form.setFields({ - packageName: { - value: release.packageName - } - }); - } + handleSubmit = e => { + e.preventDefault(); + const { uuid } = this.props.release; + const config = this.props.context; - this.setState({ - visible: true, - metaData - }); - }; + const { formConfig } = this.state; + const { specificElements } = formConfig; - handleOk = e => { + this.props.form.validateFields((err, values) => { + if (!err) { this.setState({ - visible: false, + loading: true, }); - }; + const { releaseDescription, releaseType } = values; - handleCancel = e => { - this.setState({ - visible: false, - }); - }; + const { icons, screenshots, binaryFiles } = this.state; + + const data = new FormData(); + + // add release data + const release = { + description: releaseDescription, + price: 0, + isSharedWithAllTenants: false, + metaData: JSON.stringify(this.state.metaData), + releaseType: releaseType, + }; - normFile = e => { - if (Array.isArray(e)) { - return e; + if (config.deviceTypes.mobileTypes.includes(this.props.deviceType)) { + release.supportedOsVersions = `${this.lowerOsVersion}-${this.upperOsVersion}`; } - return e && e.fileList; - }; - handleIconChange = ({fileList}) => this.setState({icons: fileList}); - handleBinaryFileChange = ({fileList}) => this.setState({binaryFiles: fileList}); - - handleScreenshotChange = ({fileList}) => this.setState({screenshots: fileList}); - - - handleSubmit = e => { - e.preventDefault(); - const {uuid} = this.props.release; - const config = this.props.context; - - const {formConfig} = this.state; - const {specificElements} = formConfig; - - this.props.form.validateFields((err, values) => { - if (!err) { - this.setState({ - loading: true - }); - const {releaseDescription, releaseType} = values; - - const {icons, screenshots, binaryFiles} = this.state; - - const data = new FormData(); - - //add release data - const release = { - description: releaseDescription, - price: 0, - isSharedWithAllTenants: false, - metaData: JSON.stringify(this.state.metaData), - releaseType: releaseType, - }; - - if ((config.deviceTypes.mobileTypes.includes(this.props.deviceType))) { - release.supportedOsVersions = `${this.lowerOsVersion}-${this.upperOsVersion}`; - } - - if (specificElements.hasOwnProperty("binaryFile") && binaryFiles.length === 1) { - data.append('binaryFile', binaryFiles[0].originFileObj); - } - - if (specificElements.hasOwnProperty("version")) { - release.version = values.version; - } - - if (specificElements.hasOwnProperty("url")) { - release.url = values.url; - } - - if (icons.length === 1) { - data.append('icon', icons[0].originFileObj); - } - - if (screenshots.length > 0) { - data.append('screenshot1', screenshots[0].originFileObj); - } - - if (screenshots.length > 1) { - data.append('screenshot2', screenshots[1].originFileObj); - } - - if (screenshots.length > 2) { - data.append('screenshot3', screenshots[2].originFileObj); - } - - const json = JSON.stringify(release); - const blob = new Blob([json], { - type: 'application/json' - }); - - data.append("applicationRelease", blob); - - const url = window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications" + formConfig.endpoint + "/" + uuid; - - axios.put( - url, - data - ).then(res => { - if (res.status === 200) { - - const updatedRelease = res.data.data; - - this.setState({ - loading: false, - visible: false, - }); - - notification["success"]({ - message: "Done!", - description: - "Saved!", - }); - // console.log(updatedRelease); - this.props.updateRelease(updatedRelease); - } - }).catch((error) => { - if (error.hasOwnProperty("response") && error.response.status === 401) { - window.location.href = window.location.origin + '/publisher/login'; - } else { - notification["error"]({ - message: "Something went wrong!", - description: - "Sorry, we were unable to complete your request.", - }); - - } - this.setState({ - loading: false - }); - }); - } - }); - }; + if ( + specificElements.hasOwnProperty('binaryFile') && + binaryFiles.length === 1 + ) { + data.append('binaryFile', binaryFiles[0].originFileObj); + } - addNewMetaData = () => { - this.setState({ - metaData: this.state.metaData.concat({'key': '', 'value': ''}) - }) - }; + if (specificElements.hasOwnProperty('version')) { + release.version = values.version; + } - handlePreviewCancel = () => this.setState({previewVisible: false}); + if (specificElements.hasOwnProperty('url')) { + release.url = values.url; + } - handlePreview = async file => { - if (!file.url && !file.preview) { - file.preview = await getBase64(file.originFileObj); + if (icons.length === 1) { + data.append('icon', icons[0].originFileObj); } - this.setState({ - previewImage: file.url || file.preview, - previewVisible: true, - }); - }; + if (screenshots.length > 0) { + data.append('screenshot1', screenshots[0].originFileObj); + } - handleLowerOsVersionChange = (lowerOsVersion) => { - this.lowerOsVersion = lowerOsVersion; - }; + if (screenshots.length > 1) { + data.append('screenshot2', screenshots[1].originFileObj); + } - handleUpperOsVersionChange = (upperOsVersion) => { - this.upperOsVersion = upperOsVersion; - }; + if (screenshots.length > 2) { + data.append('screenshot3', screenshots[2].originFileObj); + } - render() { - const { - formConfig, - icons, - screenshots, - loading, - binaryFiles, - metaData, - previewImage, - previewVisible, - binaryFileHelperText, - iconHelperText, - screenshotHelperText - } = this.state; - const {getFieldDecorator} = this.props.form; - const {isAppUpdatable, supportedOsVersions, deviceType} = this.props; - const config = this.props.context; - const uploadButton = ( -
- -
Select
-
- ); - - return ( -
- - - - -
- -
- {formConfig.specificElements.hasOwnProperty("binaryFile") && ( - - {getFieldDecorator('binaryFile', { - valuePropName: 'binaryFile', - getValueFromEvent: this.normFile, - required: true, - message: 'Please select application' - })( - false} - > - {binaryFiles.length !== 1 && ( - - )} - , - )} - - )} - - {formConfig.specificElements.hasOwnProperty("url") && ( - - {getFieldDecorator('url', { - rules: [{ - required: true, - message: 'Please input the url' - }], - })( - - )} - - )} - - {formConfig.specificElements.hasOwnProperty("version") && ( - - {getFieldDecorator('version', { - rules: [{ - required: true, - message: 'Please input the version' - }], - })( - - )} - - )} - - - {getFieldDecorator('icon', { - valuePropName: 'icon', - getValueFromEvent: this.normFile, - required: true, - message: 'Please select a icon' - })( - false} - onPreview={this.handlePreview}> - {icons.length === 1 ? null : uploadButton} - , - )} - + const json = JSON.stringify(release); + const blob = new Blob([json], { + type: 'application/json', + }); - - {getFieldDecorator('screenshots', { - valuePropName: 'icon', - getValueFromEvent: this.normFile, - required: true, - message: 'Please select a icon' - })( - false} - onPreview={this.handlePreview}> - {screenshots.length >= 3 ? null : uploadButton} - , - )} - + data.append('applicationRelease', blob); + + const url = + window.location.origin + + config.serverConfig.invoker.uri + + config.serverConfig.invoker.publisher + + '/applications' + + formConfig.endpoint + + '/' + + uuid; + + axios + .put(url, data) + .then(res => { + if (res.status === 200) { + const updatedRelease = res.data.data; + + this.setState({ + loading: false, + visible: false, + }); + + notification.success({ + message: 'Done!', + description: 'Saved!', + }); + // console.log(updatedRelease); + this.props.updateRelease(updatedRelease); + } + }) + .catch(error => { + if ( + error.hasOwnProperty('response') && + error.response.status === 401 + ) { + window.location.href = + window.location.origin + '/publisher/login'; + } else { + notification.error({ + message: 'Something went wrong!', + description: 'Sorry, we were unable to complete your request.', + }); + } + this.setState({ + loading: false, + }); + }); + } + }); + }; - - {getFieldDecorator('releaseType', { - rules: [{ - required: true, - message: 'Please input the Release Type' - }], - })( - - )} - + addNewMetaData = () => { + this.setState({ + metaData: this.state.metaData.concat({ key: '', value: '' }), + }); + }; - - {getFieldDecorator('releaseDescription', { - rules: [{ - required: true, - message: 'Please enter a description for release' - }], - })( -