From 25a6161d00d062f6d7bb7b927707e05ac72a405b Mon Sep 17 00:00:00 2001 From: Jesus Gaston Date: Tue, 12 Sep 2017 16:38:30 +0200 Subject: [PATCH 01/23] [ADD] Added domains and referer domains allowed to not to go through the filter --- .../filters/AuthenticationFilter.java | 20 +++++++++++++++++++ .../simpatico/rest/SimpaticoResourceSF.java | 1 - .../simpatico/utils/SimpaticoProperties.java | 20 +++++++++++++++++++ src/main/java/simpatico.properties | 2 ++ 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/main/java/es/hiiberia/simpatico/filters/AuthenticationFilter.java b/src/main/java/es/hiiberia/simpatico/filters/AuthenticationFilter.java index 00f4dc8..3fde9c2 100644 --- a/src/main/java/es/hiiberia/simpatico/filters/AuthenticationFilter.java +++ b/src/main/java/es/hiiberia/simpatico/filters/AuthenticationFilter.java @@ -53,7 +53,27 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } else { Logger.getRootLogger().debug("[Auth filter] IP doesnt exists in whitelist. IP: " + ipClient + ". Method: " + httpRequest.getMethod()); } + + // Check Referers domains allowed + for (String referersDomain : SimpaticoProperties.refererDomainsAllowed) { + if (SimpaticoResourceUtils.getHeader(httpRequest, "referer").contains(referersDomain)) { + Logger.getRootLogger().info("[Auth filter] Request allowed by referer domain: " + referersDomain + ". IP: " + ipClient + ". Method: " + httpRequest.getMethod()); + filterChain.doFilter(request, response); + return; + } + } + + + // Check Domains allowed + for (String domain : SimpaticoProperties.domainsAllowed) { + if (httpRequest.getRequestURI().contains(domain)) { + Logger.getRootLogger().info("[Auth filter] Request allowed without authentication. URL Requested: " + httpRequest.getRequestURL() + ". IP: " + ipClient + ". Method: " + httpRequest.getMethod()); + filterChain.doFilter(request, response); + return; + } + } + // Methods authentication Logger.getRootLogger().info("[Auth filter] Using filter. Method: " + httpRequest.getMethod()); if (httpRequest.getMethod().equalsIgnoreCase("GET")) { // User/Pass base64 compare diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java index 9ac22f9..0ec9ee8 100644 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java @@ -1,6 +1,5 @@ package es.hiiberia.simpatico.rest; -import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; diff --git a/src/main/java/es/hiiberia/simpatico/utils/SimpaticoProperties.java b/src/main/java/es/hiiberia/simpatico/utils/SimpaticoProperties.java index 42d8665..4ee50a7 100644 --- a/src/main/java/es/hiiberia/simpatico/utils/SimpaticoProperties.java +++ b/src/main/java/es/hiiberia/simpatico/utils/SimpaticoProperties.java @@ -47,6 +47,14 @@ public class SimpaticoProperties { // IPs allowed public static List ipsAllowed = new ArrayList<>(); + // Authentication domains + public static String domainNames; + // Domains allowed + public static List domainsAllowed = new ArrayList<>(); + // Referer Domains allowed + public static List refererDomainsAllowed = new ArrayList<>(); + + public static boolean getStrings() { boolean result = false; @@ -88,6 +96,18 @@ public static boolean getStrings() { ipsAllowed.add(ip.trim()); } + // Domain + String [] domains = RESOURCE_BUNDLE.getString("authentication.domains.allowed").split(","); + for (String domain : domains) { + domainsAllowed.add(domain.trim()); + } + + // Domain + String [] refererDomains = RESOURCE_BUNDLE.getString("authentication.referers.domains.allowed").split(","); + for (String refererDomain : refererDomains) { + refererDomainsAllowed.add(refererDomain.trim()); + } + result = true; } catch (MissingResourceException e) { e.printStackTrace(); diff --git a/src/main/java/simpatico.properties b/src/main/java/simpatico.properties index ca356bd..2cb9e29 100644 --- a/src/main/java/simpatico.properties +++ b/src/main/java/simpatico.properties @@ -29,6 +29,8 @@ authentication.getauth.user=simpatico authentication.getauth.pass=#s1mp4t1c0# # Allow IPs requests (For other components) authentication.whitelist.ip=localhost,127.0.0.1 +authentication.domains.allowed= +authentication.referers.domains.allowed=simpatico-dashboard # Real Ip header name (from nginx for example) http.header.realip=x-real-ip From b8ece59b50cd012d7c1d7ff73f626142c57e2031 Mon Sep 17 00:00:00 2001 From: Jesus Gaston Date: Tue, 3 Oct 2017 08:34:45 +0100 Subject: [PATCH 02/23] [ADD+IMP] Added KPIs and improved yaml file --- .../filters/AuthenticationFilter.java | 322 +++++----- .../simpatico/rest/SimpaticoResourceIFE.java | 2 +- .../simpatico/rest/SimpaticoResourceLogs.java | 601 ++++++++++++++++++ .../rest/SimpaticoResourceUtils.java | 17 +- .../simpatico/utils/UserKPIObject.java | 62 ++ src/main/resources/simpatico.properties | 6 +- .../yaml_files/swagger-simpatico-logs.yaml | 110 ++++ 7 files changed, 952 insertions(+), 168 deletions(-) create mode 100644 src/main/java/es/hiiberia/simpatico/utils/UserKPIObject.java diff --git a/src/main/java/es/hiiberia/simpatico/filters/AuthenticationFilter.java b/src/main/java/es/hiiberia/simpatico/filters/AuthenticationFilter.java index 5c6fca9..60934b4 100644 --- a/src/main/java/es/hiiberia/simpatico/filters/AuthenticationFilter.java +++ b/src/main/java/es/hiiberia/simpatico/filters/AuthenticationFilter.java @@ -22,173 +22,171 @@ public class AuthenticationFilter implements Filter { - + private static final long CACHE_VALIDITY = 1 * 60 * 60 * 1000; // 1 hour - - private static class TokenCacheEntry { - long validUntil; - String userId; - + + private static class TokenCacheEntry { + long validUntil; + String userId; + public TokenCacheEntry(String userId) { this.userId = userId; validUntil = System.currentTimeMillis() + CACHE_VALIDITY; } - - boolean isValid() { - return validUntil > System.currentTimeMillis(); - } - } - - private static ConcurrentHashMap tokenCache = new ConcurrentHashMap<>(); - - private BasicProfileService profileService = null; - - private BasicProfileService getService() { - if (profileService == null) { - synchronized (tokenCache) { - if (profileService == null) { - profileService = new BasicProfileService(SimpaticoProperties.aacUrlServer); - } - } - } - return profileService; - } - - public void init(FilterConfig filterConfig) throws ServletException { - Logger.getRootLogger().info("INIT SIMPATICO AUTHENTICATION FILTER"); - - } - - public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) { - - try { - if (Boolean.TRUE.equals(request.getAttribute(NoAuthenticationFilter.NO_AUTHENTICATION_PARAM))) { - filterChain.doFilter(request, response); - return; - } - - String ipClient = request.getRemoteAddr(); - Logger.getRootLogger().info("[Auth filter] IP request: " + ipClient); - HttpServletRequest httpRequest = (HttpServletRequest) request; - - //Logger.getRootLogger().debug(SimpaticoResourceUtils.getHeaders(httpRequest)); // Print all headers - - if (SimpaticoProperties.aacUse) { - - // Check allowed IPs - String proxyRealIp; - if ((proxyRealIp = SimpaticoResourceUtils.getRealIPHeader(httpRequest)) != null) { // If null -> Not using proxy (or header in simpatico.properties dont match) - Logger.getRootLogger().info("[Auth filter] Looks like there is a proxy server. Changing IP (" + ipClient + ") to 'real ip header' (" + proxyRealIp + ")"); - ipClient = proxyRealIp; - } - if (SimpaticoProperties.ipsAllowed.contains(ipClient)) { - Logger.getRootLogger().debug("[Auth filter] IP exists in whitelist. Method: " + httpRequest.getMethod()); - filterChain.doFilter(request, response); - return; - } else { - Logger.getRootLogger().debug("[Auth filter] IP doesnt exists in whitelist. IP: " + ipClient + ". Method: " + httpRequest.getMethod()); - } - - // Check Referers domains allowed - for (String referersDomain : SimpaticoProperties.refererDomainsAllowed) { - if (SimpaticoResourceUtils.getHeader(httpRequest, "referer").contains(referersDomain)) { - Logger.getRootLogger().info("[Auth filter] Request allowed by referer domain: " + referersDomain + ". IP: " + ipClient + ". Method: " + httpRequest.getMethod()); - filterChain.doFilter(request, response); - return; - } - } - - - // Check Domains allowed - for (String domain : SimpaticoProperties.domainsAllowed) { - if (httpRequest.getRequestURI().contains(domain)) { - Logger.getRootLogger().info("[Auth filter] Request allowed without authentication. URL Requested: " + httpRequest.getRequestURL() + ". IP: " + ipClient + ". Method: " + httpRequest.getMethod()); - filterChain.doFilter(request, response); - return; - } - } - - // Methods authentication - Logger.getRootLogger().info("[Auth filter] Using filter. Method: " + httpRequest.getMethod()); - if (httpRequest.getMethod().equalsIgnoreCase("GET")) { - // User/Pass base64 compare - String authRequest = httpRequest.getHeader("Authorization"); - Logger.getRootLogger().debug("[Auth filter] AuthRequest: " + authRequest); - - if (authRequest != null) { - String [] splitAuthRequest = authRequest.split(" "); // AuthRequest = Basic - if (splitAuthRequest.length >= 2) { - String basicAuth = SimpaticoProperties.aacGetAuthUser + ":" + SimpaticoProperties.aacGetAuthPass; - byte[] bytes = basicAuth.getBytes("UTF-8"); - String encoded = Base64.getEncoder().encodeToString(bytes); - - Logger.getRootLogger().info("[Auth filter] My user/pass to encode: " + basicAuth + ". Encoded: " + encoded + ". Recv: " + splitAuthRequest[1]); - if (encoded.equals(splitAuthRequest[1])) { - filterChain.doFilter(request, response); - return; - } else { - Logger.getRootLogger().info("[Auth filter] Basic auth dont match (" + encoded + " recv: " + splitAuthRequest[1]); - } - } - } - } else { // POST/PUT/DELETE - String token = extractHeader(httpRequest.getHeader("Authorization")); - if (token != null) { - WrappedRequest myRequestWrapper = new WrappedRequest((HttpServletRequest) request); - JSONObject jsonRequest = new JSONObject(myRequestWrapper.getBody()); - // take from cache if present - if (tokenCache.containsKey(token) && tokenCache.get(token).isValid()) { - if (jsonRequest.has("userID") && jsonRequest.getString("userID").equals(tokenCache.get(token).userId)) { - filterChain.doFilter(myRequestWrapper, response); - return; - } else { - Logger.getRootLogger().debug("[Auth filter] userId field doesnt match. Request data: " + jsonRequest.toString() + ". UserID: " + tokenCache.get(token).userId); - } - } else { - try { - BasicProfile profile = getService().getBasicProfile(token); - tokenCache.putIfAbsent(token, new TokenCacheEntry(profile.getUserId())); - if (jsonRequest.has("userID") && jsonRequest.getString("userID").equals(profile.getUserId())) { - filterChain.doFilter(myRequestWrapper, response); - return; - } else { - Logger.getRootLogger().debug("[Auth filter] userId field doesnt match. Request data: " + jsonRequest.toString() + ". Profile response: " + profile); - } + + boolean isValid() { + return validUntil > System.currentTimeMillis(); + } + } + + private static ConcurrentHashMap tokenCache = new ConcurrentHashMap<>(); + + private BasicProfileService profileService = null; + + private BasicProfileService getService() { + if (profileService == null) { + synchronized (tokenCache) { + if (profileService == null) { + profileService = new BasicProfileService(SimpaticoProperties.aacUrlServer); + } + } + } + return profileService; + } + + public void init(FilterConfig filterConfig) throws ServletException { + Logger.getRootLogger().info("INIT SIMPATICO AUTHENTICATION FILTER"); + + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) { + + try { + String ipClient = request.getRemoteAddr(); + HttpServletRequest httpRequest = (HttpServletRequest) request; + + //Logger.getRootLogger().debug(SimpaticoResourceUtils.getHeaders(httpRequest)); // Print all headers + + // Get real ip client + String proxyRealIp; + if ((proxyRealIp = SimpaticoResourceUtils.getRealIPHeader(httpRequest)) != null) { // If null -> Not using proxy (or header in simpatico.properties dont match) + Logger.getRootLogger().info("[Auth filter] Looks like there is a proxy server. Changing IP (" + ipClient + ") to 'real ip header' (" + proxyRealIp + ")"); + ipClient = proxyRealIp; + } + + // Check NO_AUTHENTICATION Param + if (Boolean.TRUE.equals(request.getAttribute(NoAuthenticationFilter.NO_AUTHENTICATION_PARAM))) { + Logger.getRootLogger().info("[Auth filter] Request allowed by NO-AUTHENTICATION Param. Request: " + httpRequest.getRequestURL() + ". IP: " + ipClient + ". Method: " + httpRequest.getMethod()); + filterChain.doFilter(request, response); + return; + } + + if (SimpaticoProperties.aacUse) { + + // Check allowed IPs + if (SimpaticoProperties.ipsAllowed.contains(ipClient)) { + Logger.getRootLogger().info("[Auth filter] Request allowed by WHITELIST IP. Request: " + httpRequest.getRequestURL() + ". IP: " + ipClient + ". Method: " + httpRequest.getMethod()); + filterChain.doFilter(request, response); + return; + } + + // Check Referers domains allowed + for (String referersDomain : SimpaticoProperties.refererDomainsAllowed) { + if (!referersDomain.isEmpty() && SimpaticoResourceUtils.getHeader(httpRequest, "referer").contains(referersDomain)) { + Logger.getRootLogger().info("[Auth filter] Request allowed by REFERER DOMAIN: " + referersDomain + ". Request: " + httpRequest.getRequestURL() + ". IP: " + ipClient + ". Method: " + httpRequest.getMethod()); + filterChain.doFilter(request, response); + return; + } + } + + // Check Domains allowed + for (String domain : SimpaticoProperties.domainsAllowed) { + if (!domain.isEmpty() && httpRequest.getRequestURL().toString().contains(domain)) { + Logger.getRootLogger().info("[Auth filter] Request allowed by DOMAIN: " + domain + ". Request: " + httpRequest.getRequestURL() + ". IP: " + ipClient + ". Method: " + httpRequest.getMethod()); + filterChain.doFilter(request, response); + return; + } + } + + // GET + if (httpRequest.getMethod().equalsIgnoreCase("GET")) { + // User/Pass base64 compare + String authRequest = httpRequest.getHeader("Authorization"); + if (authRequest != null) { + String [] splitAuthRequest = authRequest.split(" "); // AuthRequest = Basic + if (splitAuthRequest.length >= 2) { + String basicAuth = SimpaticoProperties.aacGetAuthUser + ":" + SimpaticoProperties.aacGetAuthPass; + byte[] bytes = basicAuth.getBytes("UTF-8"); + String encoded = Base64.getEncoder().encodeToString(bytes); + + if (encoded.equals(splitAuthRequest[1])) { + filterChain.doFilter(request, response); + return; + } else { + Logger.getRootLogger().info("[Auth filter] Basic auth dont match (" + encoded + " recv: " + splitAuthRequest[1] + ". Request: " + httpRequest.getRequestURL() + ". IP: " + ipClient + ". Method: " + httpRequest.getMethod()); + } + } else { + Logger.getRootLogger().warn("[Auth filter] AuthRequest array is less than 2: " + authRequest + ". Request: " + httpRequest.getRequestURL() + ". IP: " + ipClient + ". Method: " + httpRequest.getMethod()); + } + } else { + Logger.getRootLogger().info("[Auth filter] Authorization Header doesnt exists: " + authRequest + ". Request: " + httpRequest.getRequestURL() + ". IP: " + ipClient + ". Method: " + httpRequest.getMethod()); + } + } else { // POST/PUT/DELETE + String token = extractHeader(httpRequest.getHeader("Authorization")); + if (token != null) { + WrappedRequest myRequestWrapper = new WrappedRequest((HttpServletRequest) request); + JSONObject jsonRequest = new JSONObject(myRequestWrapper.getBody()); + // take from cache if present + if (tokenCache.containsKey(token) && tokenCache.get(token).isValid()) { + if (jsonRequest.has("userID") && jsonRequest.getString("userID").equals(tokenCache.get(token).userId)) { + filterChain.doFilter(myRequestWrapper, response); + return; + } else { + Logger.getRootLogger().debug("[Auth filter] userId field doesnt match. Request: " + httpRequest.getRequestURL() + ". Request data: " + jsonRequest.toString() + ". UserID: " + tokenCache.get(token).userId); + } + } else { + try { + BasicProfile profile = getService().getBasicProfile(token); + tokenCache.putIfAbsent(token, new TokenCacheEntry(profile.getUserId())); + if (jsonRequest.has("userID") && jsonRequest.getString("userID").equals(profile.getUserId())) { + filterChain.doFilter(myRequestWrapper, response); + return; + } else { + Logger.getRootLogger().debug("[Auth filter] userId field doesnt match. Request data: " + jsonRequest.toString() + ". Profile response: " + profile); + } } catch (Exception e) { - Logger.getRootLogger().error("[Auth filter] Exception: " + e.getMessage() + ". \n" + SimpaticoResourceUtils.exceptionStringifyStack(e, 10)); + Logger.getRootLogger().error("[Auth filter] Exception: " + e.getMessage() + ". \n" + SimpaticoResourceUtils.exceptionStringifyStack(e, 10)); } - - } - } - - } - - response.resetBuffer(); - response.getOutputStream().write("{\"message\": \"Access Denied\"}".getBytes()); - HttpServletResponse hsr = (HttpServletResponse) response; - hsr.setStatus(403); - return; - } else { - filterChain.doFilter(request, response); - } - } catch (Exception e) { - Logger.getRootLogger().error("[Auth filter] Exception: " + e.getMessage() + ". \n" + SimpaticoResourceUtils.exceptionStringifyStack(e, 10)); - Logger.getLogger(SimpaticoProperties.simpaticoLog_Error).error("[Auth filter] Exception: " + e.getMessage() + ". \n" + SimpaticoResourceUtils.exceptionStringifyStack(e, 10)); - } - } - - /** - * @param header - * @return - */ -private String extractHeader(String header) { - if (header != null && header.toLowerCase().startsWith("bearer ")) { - return header.substring(7); - } - return null; -} - -public void destroy() { - Logger.getRootLogger().info("DESTROY SIMPATICO AUTHENTICATION FILTER"); - } + } + } + } + + response.resetBuffer(); + response.getOutputStream().write("{\"message\": \"Access Denied\"}".getBytes()); + HttpServletResponse hsr = (HttpServletResponse) response; + hsr.setStatus(403); + return; + } else { + filterChain.doFilter(request, response); + } + } catch (Exception e) { + Logger.getRootLogger().error("[Auth filter] Exception: " + e.getMessage() + ". \n" + SimpaticoResourceUtils.exceptionStringifyStack(e, 50)); + Logger.getLogger(SimpaticoProperties.simpaticoLog_Error).error("[Auth filter] Exception: " + e.getMessage() + ". \n" + SimpaticoResourceUtils.exceptionStringifyStack(e, 10)); + } + } + + /** + * @param header + * @return + */ + private String extractHeader(String header) { + if (header != null && header.toLowerCase().startsWith("bearer ")) { + return header.substring(7); + } + return null; + } + + public void destroy() { + Logger.getRootLogger().info("DESTROY SIMPATICO AUTHENTICATION FILTER"); + } } \ No newline at end of file diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceIFE.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceIFE.java index 9d3bc89..81eb6a0 100644 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceIFE.java +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceIFE.java @@ -46,7 +46,7 @@ public class SimpaticoResourceIFE { private static String EVENT_SESSION_START = "session_start"; private static String EVENT_SESSION_END = "session_end"; private static String EVENT_FORM_START = "form_start"; - private static String EVENT_FORM_END = "form_end"; + public static String EVENT_FORM_END = "form_end"; private static String EVENT_ELEMENT_CLICKS = "elements_clicks"; private static int numLinesPrintStackInternalError = 1; diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceLogs.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceLogs.java index 78924e4..b6fcfe1 100644 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceLogs.java +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceLogs.java @@ -1,5 +1,14 @@ package es.hiiberia.simpatico.rest; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + import javax.servlet.http.HttpServletRequest; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -13,8 +22,12 @@ import javax.ws.rs.core.UriInfo; import org.apache.log4j.Logger; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; import es.hiiberia.simpatico.utils.SimpaticoProperties; +import es.hiiberia.simpatico.utils.UserKPIObject; +import es.hiiberia.simpatico.utils.Utils; @Path("/logs") public class SimpaticoResourceLogs { @@ -26,6 +39,594 @@ public class SimpaticoResourceLogs { private static String THIS_RESOURCE = "Logs"; private static int numLinesPrintStackInternalError = 1; + private static int MAX_NUM_RESULTS_REQUEST = 1000; + + /* KPIs */ + @POST + @Path("/reduction-time-spent") + @Produces(MediaType.APPLICATION_JSON) + public Response reductionTimeSpent(@Context HttpServletRequest request, String postData) { + try { + // IDs user using SIMPATICO + // IDS user without using simpatico + // Date + + JSONObject json = Utils.createJSONObjectIfValid(postData); + if (json != null) { + long timeSpentNoSimpaticoUsers = 0; + long timeSpentSimpaticoUsers = 0; + + // Testing mode + Boolean testing = json.optBoolean("testing"); + if (testing) { + timeSpentSimpaticoUsers = json.optLong("averageTimeSpentUsersSimpatico", -1); + timeSpentNoSimpaticoUsers = json.optLong("averageTimeSpentUsersWithoutSimpatico", -1); + Logger.getRootLogger().debug("[LOGS] Testing Mode. averageTimeSpentUsersSimpatico: " + timeSpentNoSimpaticoUsers + ", averageTimeSpentUsersWithoutSimpatico: " + timeSpentSimpaticoUsers); + if (timeSpentNoSimpaticoUsers == -1 || timeSpentSimpaticoUsers == -1) { + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "JSON in bad format. averageTimeSpentUsersSimpatico and averageTimeSpentUsersWithoutSimpatico keys dont found"); + } + } else { + String dateStart = json.opt("dateStart").toString(); + String dateEnd = json.opt("dateEnd").toString(); + List usersSimpatico = Utils.toList(json.getJSONArray("usersSimpatico")); + List usersWithoutSimpatico = Utils.toList(json.getJSONArray("usersWithoutSimpatico")); + + + String usersSimpaticoString = new String(); + for (int i = 0; i < usersSimpatico.size(); i++) { + if (i == usersSimpatico.size() - 1 ) { // Last iteration + usersSimpaticoString += "\"" + usersSimpatico.get(i).toString() + "\""; + } else { + usersSimpaticoString += "\"" + usersSimpatico.get(i).toString() + "\","; + } + } + + String usersWithoutSimpaticoString = new String(); + for (int i = 0; i < usersWithoutSimpatico.size(); i++) { + if (i == usersWithoutSimpatico.size() - 1 ) { // Last iteration + usersWithoutSimpaticoString += "\"" + usersWithoutSimpatico.get(i).toString() + "\""; + } else { + usersWithoutSimpaticoString += "\"" + usersWithoutSimpatico.get(i).toString() + "\","; + } + } + + // Check params + + + // Do first request + // First query + String query1 = new String( + "{\n" + + " \"query\": {\n" + + " \"bool\" : {\n " + + " \"must\" : {\n" + + " \"range\" : {\n" + + " \"created\" : {\n" + + " \"gte\": \"" + dateStart + "\",\n" + + " \"lt\": \"" + dateEnd + "\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"filter\" : {\n" + + " \"terms\" : {\n" + + " \"userID\" : [" + usersSimpaticoString + "]\n" + + " } \n" + + " },\n" + + " \"must_not\" : [\n" + + " {\"match\" : { \"event\": \"elements_clicks\"} }, \n" + + " {\"match\" : { \"event\": \"session_start\"} }, \n" + + " {\"match\" : { \"event\": \"session_end\"} } \n" + + " ]\n" + + " }\n" + + " },\n" + + " \"sort\": {\n" + + " \"created\": { \"order\": \"asc\" }\n" + + " }\n" + + "}\n"); + + + URL url = new URL("http://" + SimpaticoProperties.elasticSearchIp + ":9200/shared/IFE/_search?size=" + MAX_NUM_RESULTS_REQUEST); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("Accept", "application/json"); + OutputStreamWriter osw = new OutputStreamWriter(connection.getOutputStream()); + osw.write(query1); + osw.flush(); + osw.close(); + Logger.getRootLogger().debug("[LOGS] Sending first GET to url: " + url); + Logger.getRootLogger().debug("[LOGS] Response code: " + connection.getResponseCode()); + + BufferedReader in = new BufferedReader( + new InputStreamReader(connection.getInputStream())); + String inputLine; + StringBuffer response = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + + //print result + Logger.getRootLogger().debug("[LOGS] Response: " + response.toString()); + + + + // Second request + String queryNoSimpatico = new String( + "{\n" + + " \"query\": {\n" + + " \"bool\" : {\n " + + " \"must\" : {\n" + + " \"range\" : {\n" + + " \"created\" : {\n" + + " \"gte\": \"" + dateStart + "\",\n" + + " \"lt\": \"" + dateEnd + "\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"filter\" : {\n" + + " \"terms\" : {\n" + + " \"userID\" : [" + usersWithoutSimpaticoString + "]\n" + + " } \n" + + " },\n" + + " \"must_not\" : [\n" + + " {\"match\" : { \"event\": \"elements_clicks\"} }, \n" + + " {\"match\" : { \"event\": \"session_start\"} }, \n" + + " {\"match\" : { \"event\": \"session_end\"} } \n" + + " ]\n" + + " }\n" + + " },\n" + + " \"sort\": {\n" + + " \"created\": { \"order\": \"asc\" }\n" + + " }\n" + + "}\n"); + + + URL url2 = new URL("http://" + SimpaticoProperties.elasticSearchIp + ":9200/shared/IFE/_search?size=" + MAX_NUM_RESULTS_REQUEST); + HttpURLConnection connection2 = (HttpURLConnection) url.openConnection(); + connection2.setRequestMethod("GET"); + connection2.setDoOutput(true); + connection2.setRequestProperty("Content-Type", "application/json"); + connection2.setRequestProperty("Accept", "application/json"); + OutputStreamWriter osw2 = new OutputStreamWriter(connection2.getOutputStream()); + osw2.write(queryNoSimpatico); + osw2.flush(); + osw2.close(); + + Logger.getRootLogger().debug("[LOGS] Sending second GET to url: " + url2); + Logger.getRootLogger().debug("[LOGS] Response code: " + connection2.getResponseCode()); + + BufferedReader in2 = new BufferedReader( + new InputStreamReader(connection2.getInputStream())); + String inputLine2; + StringBuffer response2 = new StringBuffer(); + + while ((inputLine2 = in2.readLine()) != null) { + response2.append(inputLine2); + } + in2.close(); + + //print result + Logger.getRootLogger().debug("[LOGS] Response: " + response2.toString()); + + // Convert each response to json and calculate percentage + JSONObject jsonUsersSimpatico = Utils.createJSONObjectIfValid(response.toString()); + JSONObject jsonUsersWithoutSimpatico = Utils.createJSONObjectIfValid(response2.toString()); + + // Arrays to process data from elastic search and get how time users spent + ArrayList alUsersSimpatico = new ArrayList<>(); + ArrayList alUsersWithoutSimpatico = new ArrayList<>(); + + // Process Simpatico users + JSONObject jsonUsersSimpaticoResults = jsonUsersSimpatico.getJSONObject("hits"); + if (jsonUsersSimpaticoResults.getInt("total") > 0) { + JSONObject hitSource; + String event; + String userId; + String timestamp; + //String created; + Date timeDate; + JSONArray arrayHitsUsersSimpatico = jsonUsersSimpaticoResults.getJSONArray("hits"); + for (int i = 0; i < arrayHitsUsersSimpatico.length(); i++) { + hitSource = arrayHitsUsersSimpatico.getJSONObject(i).getJSONObject("_source"); + userId = hitSource.getString("userID"); + event = hitSource.getString("event"); + timestamp = hitSource.getString("timestamp"); + //created = hitSource.getString("created"); + //DateTime dateTime = ISODateTimeFormat.dateTimeParser().parseDateTime(timestamp); + //timeDate = dateTime.toDate(); + timeDate = new Date(Long.parseLong(timestamp)); + if (event.equalsIgnoreCase("form_start")) { + alUsersSimpatico.add(0, new UserKPIObject(userId, timeDate)); + } else { + // Search first ocurrence with these ID and add it. If doesnt find any previous item, it seems that event form_start doesnt occurs, so we dont save + for (UserKPIObject oUser : alUsersSimpatico) { + if (oUser.getUser().equalsIgnoreCase(userId)) { + oUser.addDate(timeDate, event); + } + } + } + } + } + + // Process No Simpatico users + JSONObject jsonUsersWithoutSimpaticoResults = jsonUsersWithoutSimpatico.getJSONObject("hits"); + if (jsonUsersWithoutSimpaticoResults.getInt("total") > 0) { + JSONObject hitSource; + String userId; + String timestamp; + String event; + //String created; + Date timeDate; + JSONArray arrayHitsUsersNoSimpatico = jsonUsersWithoutSimpaticoResults.getJSONArray("hits"); + for (int i = 0; i < arrayHitsUsersNoSimpatico.length(); i++) { + hitSource = arrayHitsUsersNoSimpatico.getJSONObject(i).getJSONObject("_source"); + userId = hitSource.getString("userID"); + event = hitSource.getString("event"); + timestamp = hitSource.getString("timestamp"); + //created = hitSource.getString("created"); + //DateTime dateTime = ISODateTimeFormat.dateTimeParser().parseDateTime(timestamp); + //timeDate = dateTime.toDate(); + timeDate = new Date(Long.parseLong(timestamp)); + if (event.equalsIgnoreCase("form_start")) { + alUsersWithoutSimpatico.add(0, new UserKPIObject(userId, timeDate)); + } else { + Logger.getRootLogger().debug("[LOGS] Response: " + response2.toString()); + // Search first ocurrence with these ID and add it. If doesnt find any previous item, it seems that event form_start doesnt occurs, so we dont save + for (UserKPIObject oUser : alUsersWithoutSimpatico) { + if (oUser.getUser().equalsIgnoreCase(userId)) { + oUser.addDate(timeDate, event); + } + } + } + } + } + + // Average time spent simpatico Users + for (UserKPIObject oUser : alUsersSimpatico) { + timeSpentSimpaticoUsers += oUser.getDiffDateMS(); + } + if (alUsersSimpatico.size() > 0) { + timeSpentSimpaticoUsers = timeSpentSimpaticoUsers / alUsersSimpatico.size(); + } + + // Average time spent NO simpatico Users + for (UserKPIObject oUser : alUsersWithoutSimpatico) { + timeSpentNoSimpaticoUsers += oUser.getDiffDateMS(); + } + if (alUsersWithoutSimpatico.size() > 0) { + timeSpentNoSimpaticoUsers = timeSpentNoSimpaticoUsers / alUsersWithoutSimpatico.size(); + } + } + + // Page to do percentage. Inverse because less time is better https://www.skillsyouneed.com/num/percent-change.html + double percentage; + boolean decrease = false; + if (timeSpentNoSimpaticoUsers == 0) { + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, "Infinity"); + } else { + double sup = timeSpentNoSimpaticoUsers - timeSpentSimpaticoUsers; + double inf = timeSpentNoSimpaticoUsers; + if (sup < 0) { // There is a decrease + decrease = true; + sup = timeSpentSimpaticoUsers - timeSpentNoSimpaticoUsers; + inf = timeSpentSimpaticoUsers; + } + percentage = Math.round((sup/inf * 100) * 100.0) / 100.0; + } + + String valueReturn; + if (decrease) { + valueReturn = "-" + String.valueOf(percentage) + "%"; + } else { + valueReturn = String.valueOf(percentage) + "%"; + } + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, valueReturn); + + /*double percentage = timeSpentSimpaticoUsers / timeSpentNoSimpaticoUsers; + percentage = Math.round((percentage * 100) * 100.0) / 100.0; + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, percentage + "%");*/ + } + + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "JSON in bad format"); + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + } + + @POST + @Path("/percentage-complete-autonomously") + @Produces(MediaType.APPLICATION_JSON) + public Response percentageCompleteAutonomously(@Context HttpServletRequest request, String postData) { + try { + // IDs user using SIMPATICO + // IDS user without using simpatico + // Date + + JSONObject json = Utils.createJSONObjectIfValid(postData); + if (json != null) { + int usersCompleteFormSimpatico = 0; + int usersCompleteFormWithoutSimpatico = 0; + + // Testing mode + Boolean testing = json.optBoolean("testing"); + if (testing) { + usersCompleteFormSimpatico = json.optInt("usersSimpaticoComplete", -1); + usersCompleteFormWithoutSimpatico = json.optInt("usersWithoutSimpaticoComplete", -1); + Logger.getRootLogger().debug("[LOGS] Testing Mode. usersSimpaticoComplete: " + usersCompleteFormSimpatico + ", usersWithoutSimpatico: " + usersCompleteFormWithoutSimpatico); + if (usersCompleteFormSimpatico == -1 || usersCompleteFormWithoutSimpatico == -1) { + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "JSON in bad format. usersSimpatico and usersWithoutSimpatico keys dont found"); + } + } else { + String dateStart = json.opt("dateStart").toString(); + String dateEnd = json.opt("dateEnd").toString(); + List usersSimpatico = Utils.toList(json.getJSONArray("usersSimpatico")); + List usersWithoutSimpatico = Utils.toList(json.getJSONArray("usersWithoutSimpatico")); + + String usersSimpaticoString = new String(); + for (int i = 0; i < usersSimpatico.size(); i++) { + if (i == usersSimpatico.size() - 1 ) { // Last iteration + usersSimpaticoString += "\"" + usersSimpatico.get(i).toString() + "\""; + } else { + usersSimpaticoString += "\"" + usersSimpatico.get(i).toString() + "\","; + } + } + + String usersWithoutSimpaticoString = new String(); + for (int i = 0; i < usersWithoutSimpatico.size(); i++) { + if (i == usersWithoutSimpatico.size() - 1 ) { // Last iteration + usersWithoutSimpaticoString += "\"" + usersWithoutSimpatico.get(i).toString() + "\""; + } else { + usersWithoutSimpaticoString += "\"" + usersWithoutSimpatico.get(i).toString() + "\","; + } + } + + + // Do first request + // First query + String query1 = new String( + "{\n" + + " \"query\": {\n" + + " \"bool\" : {\n " + + " \"must\" : {\n" + + " \"range\" : {\n" + + " \"created\" : {\n" + + " \"gte\": \"" + dateStart + "\",\n" + + " \"lt\": \"" + dateEnd + "\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"filter\" : {\n" + + " \"terms\" : {\n" + + " \"userID\" : [" + usersSimpaticoString + "]\n" + + " } \n" + + " },\n" + + " \"must_not\" : [\n" + + " {\"match\" : { \"event\": \"elements_clicks\"} }, \n" + + " {\"match\" : { \"event\": \"session_start\"} }, \n" + + " {\"match\" : { \"event\": \"session_end\"} } \n" + + " ]\n" + + " }\n" + + " },\n" + + " \"sort\": {\n" + + " \"created\": { \"order\": \"asc\" }\n" + + " }\n" + + "}\n"); + + + URL url = new URL("http://" + SimpaticoProperties.elasticSearchIp + ":9200/shared/IFE/_search?size=" + MAX_NUM_RESULTS_REQUEST); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("Accept", "application/json"); + OutputStreamWriter osw = new OutputStreamWriter(connection.getOutputStream()); + osw.write(query1); + osw.flush(); + osw.close(); + Logger.getRootLogger().debug("[LOGS] Sending first GET to url: " + url); + Logger.getRootLogger().debug("[LOGS] Response code: " + connection.getResponseCode()); + + BufferedReader in = new BufferedReader( + new InputStreamReader(connection.getInputStream())); + String inputLine; + StringBuffer response = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + + //print result + Logger.getRootLogger().debug("[LOGS] Response: " + response.toString()); + + + + // Second request + String queryNoSimpatico = new String( + "{\n" + + " \"query\": {\n" + + " \"bool\" : {\n " + + " \"must\" : {\n" + + " \"range\" : {\n" + + " \"created\" : {\n" + + " \"gte\": \"" + dateStart + "\",\n" + + " \"lt\": \"" + dateEnd + "\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"filter\" : {\n" + + " \"terms\" : {\n" + + " \"userID\" : [" + usersWithoutSimpaticoString + "]\n" + + " } \n" + + " },\n" + + " \"must_not\" : [\n" + + " {\"match\" : { \"event\": \"elements_clicks\"} }, \n" + + " {\"match\" : { \"event\": \"session_start\"} }, \n" + + " {\"match\" : { \"event\": \"session_end\"} } \n" + + " ]\n" + + " }\n" + + " },\n" + + " \"sort\": {\n" + + " \"created\": { \"order\": \"asc\" }\n" + + " }\n" + + "}\n"); + + + URL url2 = new URL("http://" + SimpaticoProperties.elasticSearchIp + ":9200/shared/IFE/_search?size=" + MAX_NUM_RESULTS_REQUEST); + HttpURLConnection connection2 = (HttpURLConnection) url.openConnection(); + connection2.setRequestMethod("GET"); + connection2.setDoOutput(true); + connection2.setRequestProperty("Content-Type", "application/json"); + connection2.setRequestProperty("Accept", "application/json"); + OutputStreamWriter osw2 = new OutputStreamWriter(connection2.getOutputStream()); + osw2.write(queryNoSimpatico); + osw2.flush(); + osw2.close(); + + Logger.getRootLogger().debug("[LOGS] Sending second GET to url: " + url2); + Logger.getRootLogger().debug("[LOGS] Response code: " + connection2.getResponseCode()); + + BufferedReader in2 = new BufferedReader( + new InputStreamReader(connection2.getInputStream())); + String inputLine2; + StringBuffer response2 = new StringBuffer(); + + while ((inputLine2 = in2.readLine()) != null) { + response2.append(inputLine2); + } + in2.close(); + + //print result + Logger.getRootLogger().debug("[LOGS] Response: " + response2.toString()); + + // Convert each response to json and calculate percentage + JSONObject jsonUsersSimpatico = Utils.createJSONObjectIfValid(response.toString()); + JSONObject jsonUsersWithoutSimpatico = Utils.createJSONObjectIfValid(response2.toString()); + + // Arrays to process data from elastic search and get how time users spent + ArrayList alUsersSimpatico = new ArrayList<>(); + ArrayList alUsersWithoutSimpatico = new ArrayList<>(); + + // Process Simpatico users + JSONObject jsonUsersSimpaticoResults = jsonUsersSimpatico.getJSONObject("hits"); + if (jsonUsersSimpaticoResults.getInt("total") > 0) { + JSONObject hitSource; + String event; + String userId; + String timestamp; + //String created; + Date timeDate; + JSONArray arrayHitsUsersSimpatico = jsonUsersSimpaticoResults.getJSONArray("hits"); + for (int i = 0; i < arrayHitsUsersSimpatico.length(); i++) { + hitSource = arrayHitsUsersSimpatico.getJSONObject(i).getJSONObject("_source"); + userId = hitSource.getString("userID"); + event = hitSource.getString("event"); + timestamp = hitSource.getString("timestamp"); + //created = hitSource.getString("created"); + //DateTime dateTime = ISODateTimeFormat.dateTimeParser().parseDateTime(timestamp); + //timeDate = dateTime.toDate(); + timeDate = new Date(Long.parseLong(timestamp)); + if (event.equalsIgnoreCase("form_start")) { + alUsersSimpatico.add(0, new UserKPIObject(userId, timeDate)); + } else { + // Search first ocurrence with these ID and add it. If doesnt find any previous item, it seems that event form_start doesnt occurs, so we dont save + for (UserKPIObject oUser : alUsersSimpatico) { + if (oUser.getUser().equalsIgnoreCase(userId)) { + oUser.addDate(timeDate, event); + } + } + } + } + } + + // Process No Simpatico users + JSONObject jsonUsersWithoutSimpaticoResults = jsonUsersWithoutSimpatico.getJSONObject("hits"); + if (jsonUsersWithoutSimpaticoResults.getInt("total") > 0) { + JSONObject hitSource; + String userId; + String timestamp; + String event; + //String created; + Date timeDate; + JSONArray arrayHitsUsersNoSimpatico = jsonUsersWithoutSimpaticoResults.getJSONArray("hits"); + for (int i = 0; i < arrayHitsUsersNoSimpatico.length(); i++) { + hitSource = arrayHitsUsersNoSimpatico.getJSONObject(i).getJSONObject("_source"); + userId = hitSource.getString("userID"); + event = hitSource.getString("event"); + timestamp = hitSource.getString("timestamp"); + //created = hitSource.getString("created"); + //DateTime dateTime = ISODateTimeFormat.dateTimeParser().parseDateTime(timestamp); + //timeDate = dateTime.toDate(); + timeDate = new Date(Long.parseLong(timestamp)); + if (event.equalsIgnoreCase("form_start")) { + alUsersWithoutSimpatico.add(0, new UserKPIObject(userId, timeDate)); + } else { + Logger.getRootLogger().debug("[LOGS] Response: " + response2.toString()); + // Search first ocurrence with these ID and add it. If doesnt find any previous item, it seems that event form_start doesnt occurs, so we dont save + for (UserKPIObject oUser : alUsersWithoutSimpatico) { + if (oUser.getUser().equalsIgnoreCase(userId)) { + oUser.addDate(timeDate, event); + } + } + } + } + } + + // Users with SIMPATICO that complete form + for (UserKPIObject oUser : alUsersSimpatico) { + if (oUser.isFormComplete()) { + usersCompleteFormSimpatico++; + } + } + + // Users without SIMPATICO that complete form + for (UserKPIObject oUser : alUsersWithoutSimpatico) { + if (oUser.isFormComplete()) { + usersCompleteFormWithoutSimpatico++; + } + } + } + + // Page to do percentage https://www.skillsyouneed.com/num/percent-change.html + double percentage; + boolean decrease = false; + if (usersCompleteFormWithoutSimpatico == 0) { + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, "Infinity"); + } else { + double sup = usersCompleteFormSimpatico - usersCompleteFormWithoutSimpatico; + double inf = usersCompleteFormWithoutSimpatico; + if (sup < 0) { // There is a decrease + decrease = true; + sup = usersCompleteFormWithoutSimpatico - usersCompleteFormSimpatico; + inf = usersCompleteFormWithoutSimpatico; + } + percentage = Math.round((sup/inf * 100) * 100.0) / 100.0; + } + + String valueReturn; + if (decrease) { + valueReturn = "-" + String.valueOf(percentage) + "%"; + } else { + valueReturn = String.valueOf(percentage) + "%"; + } + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, valueReturn); + + /*double percentage = usersCompleteFormSimpatico / usersCompleteFormWithoutSimpatico; + percentage = Math.round((percentage * 100) * 100.0) / 100.0; // Round to 2 decimals + + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, percentage + "%");*/ + } + + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "JSON in bad format"); + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + } @GET @Path("/find/") diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceUtils.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceUtils.java index e4ecebd..6374a3a 100644 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceUtils.java +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceUtils.java @@ -173,6 +173,7 @@ public static Response findRequest(HttpServletRequest request, UriInfo uriInfo, ArrayList literalWords = new ArrayList<>(); int limit = 0; String fieldSortName = ""; + String fieldSearch = ""; SortOrder sortOrder = SortOrder.ASC; // Inicialize. If fieldSort is empty dont sort // Query params @@ -197,6 +198,12 @@ public static Response findRequest(HttpServletRequest request, UriInfo uriInfo, if (!values.isEmpty() && Utils.isInteger(values.get(0))) { limit = Integer.parseInt(values.get(0)); } + // Field Search + } else if (key.contentEquals("field")) { + if (!values.isEmpty()) { + fieldSearch = values.get(0); + //limit = Integer.parseInt(values.get(0)); + } // Sort } else if (key.contentEquals(SimpaticoResourceUtils.sortASCParam)) { fieldSortName = SimpaticoProperties.elasticSearchCreatedFieldName; @@ -213,9 +220,14 @@ public static Response findRequest(HttpServletRequest request, UriInfo uriInfo, SearchResponse responseES; // No params, so empty request -> return full documents stored - if (literalWords.isEmpty()) { + if (!fieldSearch.isEmpty()) { + //Logger.getRootLogger().info("[RESOURCE_UTILS] FINDING with field"); + responseES = ElasticSearchConnector.getInstance().search(ES_INDEX, ES_TYPE, fieldSearch, literalWords, fieldSortName, sortOrder, limit); + } else if (literalWords.isEmpty()) { + //Logger.getRootLogger().info("[RESOURCE_UTILS] NOP"); responseES = ElasticSearchConnector.getInstance().search(ES_INDEX, ES_TYPE, fieldSortName, sortOrder, limit); } else { + //Logger.getRootLogger().info("[RESOURCE_UTILS] NOP"); responseES = ElasticSearchConnector.getInstance().search(ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, literalWords, fieldSortName, sortOrder, limit); } @@ -458,7 +470,8 @@ public static String getHeaders (HttpServletRequest request) { */ public static String getHeader (HttpServletRequest request, String header) { - return request.getHeader(header); + String ret = request.getHeader(header); + return ret != null ? ret: ""; } /** diff --git a/src/main/java/es/hiiberia/simpatico/utils/UserKPIObject.java b/src/main/java/es/hiiberia/simpatico/utils/UserKPIObject.java new file mode 100644 index 0000000..96019cf --- /dev/null +++ b/src/main/java/es/hiiberia/simpatico/utils/UserKPIObject.java @@ -0,0 +1,62 @@ +package es.hiiberia.simpatico.utils; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.apache.log4j.Logger; + +import es.hiiberia.simpatico.rest.SimpaticoResourceIFE; + +public class UserKPIObject { + + private String user; + private Date initial; + private Date end; + private boolean formComplete; + private List data; + + public UserKPIObject(String user, Date date) { + this.user = user; + this.initial = date; + this.end = date; + this.formComplete = false; + this.data = new ArrayList<>(); + } + + public String getUser() { + return this.user; + } + + public Date getInitialDate() { + return this.initial; + } + + public Date getEndDate() { + return this.end; + } + + public long getDiffDateMS() { + return this.end.getTime() - this.initial.getTime(); + } + + public boolean isFormComplete() { + return this.formComplete; + } + + public void addDate(Date date, String event) { + if (date.after(this.end)) { + this.end = date; + } else if (date.before(this.initial)) { + this.initial = date; + } else { + Logger.getRootLogger().debug("[USER-KPI-OBJECT] Date not used. this.initial: " + this.initial.toString() + ", this.end: " + this.end.toString() + ", date to add: " + date.toString()); + } + + if (event.equalsIgnoreCase(SimpaticoResourceIFE.EVENT_FORM_END)) { + this.formComplete = true; + } + + data.add("date: " + date + ", date milis: "+ date.getTime()); + } +} diff --git a/src/main/resources/simpatico.properties b/src/main/resources/simpatico.properties index 57e4890..ee1c023 100644 --- a/src/main/resources/simpatico.properties +++ b/src/main/resources/simpatico.properties @@ -21,14 +21,14 @@ elasticsearch.search.field=_all # Authentication (AAC) authentication.use=true -authentication.url=https://tn.smartcommunitylab.it/aac +authentication.url=https://simpatico.hi-iberia.es:4570/aac # AAC Get auth. Basic auth. Header Authorization: Basic authentication.getauth.user=simpatico authentication.getauth.pass=#s1mp4t1c0# # Allow IPs requests (For other components) authentication.whitelist.ip=localhost,127.0.0.1 -authentication.domains.allowed= -authentication.referers.domains.allowed=simpatico-dashboard +authentication.domains.allowed=simpatico.hi-iberia.es:4570/simpatico/api/logs/find,simpatico.hi-iberia.es:4570/simpatico/api/ife/find,simpatico.hi-iberia.es:4570/simpatico/api/logs/reduction-time-spent,simpatico.hi-iberia.es:4570/simpatico/api/logs/percentage-complete-autonomously +authentication.referers.domains.allowed=simpatico-dashboard,simpatico.hi-iberia.es:4570/IFE/ # Real Ip header name (from nginx for example) http.header.realip=x-real-ip diff --git a/src/main/webapp/dist/yaml_files/swagger-simpatico-logs.yaml b/src/main/webapp/dist/yaml_files/swagger-simpatico-logs.yaml index 2f45957..eed2d68 100644 --- a/src/main/webapp/dist/yaml_files/swagger-simpatico-logs.yaml +++ b/src/main/webapp/dist/yaml_files/swagger-simpatico-logs.yaml @@ -10,6 +10,62 @@ schemes: produces: - "application/json" paths: + /reduction-time-spent: + post: + tags: + - "Time spent" + summary: "Time spent completing form" + description: "Get increase or decrease percentage between time spent by users using Simpatico and those who do not use Simpatico.\nDates in UTC String" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "query" + description: "A valid JSON to insert." + required: true + schema: + $ref: "#/definitions/Message_KPI_Time" + responses: + 200: + description: "A percentage or Infinity." + schema: + $ref: "#/definitions/Message" + 400: + description: "Bad request." + 500: + description: "Unexpected error." + schema: + $ref: "#/definitions/Message" + /percentage-complete-autonomously: + post: + tags: + - "Complete autonomously" + summary: "Time spent completing form" + description: "Get increase or decrease percentage between users that fill out completely the form using Simpatico and those who do not use Simpatico.\n" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "query" + description: "A valid JSON to insert." + required: true + schema: + $ref: "#/definitions/Message_KPI_Complete_Form" + responses: + 200: + description: "A percentage or Infinity." + schema: + $ref: "#/definitions/Message" + 400: + description: "Bad request." + 500: + description: "Unexpected error." + schema: + $ref: "#/definitions/Message" /find: get: tags: @@ -186,11 +242,65 @@ definitions: id: type: "string" description: "Unique identifier representing a specific document." + Array_Users: + type: "string" Message: type: "object" properties: message: type: "string" + Message_KPI_Time: + type: "object" + properties: + usersSimpatico: + type: "array" + items: + $ref: "#/definitions/Array_Users" + usersWithoutSimpatico: + type: "array" + items: + $ref: "#/definitions/Array_Users" + dateStart: + type: "string" + description: "Date in UTC" + dateEnd: + type: "string" + description: "Date in UTC" + testing: + type: "boolean" + description: "If true, it use values in keys averageTimeSpentUsersSimpatico and averageTimeSpentUsersWithoutSimpatico" + averageTimeSpentUsersSimpatico: + type: "integer" + description: "Average time spent by users using Simpatico" + averageTimeSpentUsersWithoutSimpatico: + type: "integer" + description: "Average time spent by users without using Simpatico" + Message_KPI_Complete_Form: + type: "object" + properties: + usersSimpatico: + type: "array" + items: + $ref: "#/definitions/Array_Users" + usersWithoutSimpatico: + type: "array" + items: + $ref: "#/definitions/Array_Users" + dateStart: + type: "string" + description: "Date in UTC" + dateEnd: + type: "string" + description: "Date in UTC" + testing: + type: "boolean" + description: "If true, it use values in keys usersSimpaticoComplete and usersWithoutSimpaticoComplete" + usersSimpaticoComplete: + type: "integer" + description: "Users array to check if them complete the form" + usersWithoutSimpaticoComplete: + type: "integer" + description: "Users array to check if them complete the form" inline_response_200: properties: count: From d319183703b5919c69ba8f02b626c7c3b5f344a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20M=C3=A9ndez?= Date: Wed, 18 Oct 2017 10:18:32 +0200 Subject: [PATCH 03/23] [IMP] SF italian translation and new wae questions --- .../es/hiiberia/simpatico/utils/Forms.java | 52 ++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/src/main/java/es/hiiberia/simpatico/utils/Forms.java b/src/main/java/es/hiiberia/simpatico/utils/Forms.java index 692454f..739fa8d 100644 --- a/src/main/java/es/hiiberia/simpatico/utils/Forms.java +++ b/src/main/java/es/hiiberia/simpatico/utils/Forms.java @@ -25,6 +25,9 @@ public class Forms { private String timeout; private String timeout_placeholder; + private String wae_steps; + private String wae_blocks; + public Forms(String lang) { if (lang.equals("es")) { /** Spanish **/ @@ -45,6 +48,9 @@ public Forms(String lang) { timeout = "La sesión tardó en completarse más de lo acostumbrado. ¿Tuvo algún problema con algún elemento del servicio?"; timeout_placeholder = "Explique los problemas encontrados..."; + + wae_steps = "Fue útil la guía paso a paso?"; + wae_blocks = "Fue útil la organizacin en bloques del servicio?"; } else if (lang.equals("it")) { /** Italian **/ button_cancel = "Annulla"; @@ -52,18 +58,21 @@ public Forms(String lang) { slider_useful = "Molto utile"; slider_unuseful = "Non è utile"; - common_faces = "Pensi che insieme strumenti di SIMPATICO ti ha facilitato la comprensione del servizio?"; + common_faces = "Pensi che gli strumenti di SIMPATICO falicilitino la comprensione del servizio e la compilazione del modulo?"; common_comments = "Hai qualche suggerimento per gli sviluppatori di SIMPATICO?"; common_opinion_placeholder = "Scrivi una recensione..."; - simpl_paragraph = "E 'stato semplificando punto che ha chiamato utile per capire meglio il servizio?"; - simpl_phrase = "È stata la semplificazione della frase che ha chiamato utile per capire meglio il servizio?"; - simpl_word = "E 'stato semplificando parola chiamata utile per capire meglio il servizio?"; + simpl_paragraph = "La semplificazione del paragrafo ti ha aiutato a capire meglio il senso di quanto richiesto?"; + simpl_phrase = "La semplificazione della frase ti ha aiutato a capire meglio il senso di quanto richiesto?"; + simpl_word = "La semplificazione della singola parola ti ha aiutato a capire meglio il senso di quanto richiesto?"; - ctz_slider = "Quanto utile è stata la sua richiesta al Citizenpedia?"; + ctz_slider = "Quanto sono state utili le informazioni disponibili in Citizenpedia?"; - timeout = "La sessione ha preso per completare più del solito. Hai avuto problemi con qualsiasi elemento del servizio?"; + timeout = "La tua sessione ha richiesto piu' tempo del previsto. Hai incontrato qualche difficoltà nella compilazione del modulo?"; timeout_placeholder = "Spiegare i problemi incontrati..."; + + wae_steps = "Quanto e' stata utile la guida passo passo?"; + wae_blocks = "Quanto e' stata utile l'organizzazione in blocchi del modulo?"; } else { /** English (default) **/ button_cancel = "Cancel"; @@ -83,6 +92,9 @@ public Forms(String lang) { timeout = "The session took longer than usual. Did you have a problem with any element of the service?"; timeout_placeholder = "Explain the problems encountered..."; + + wae_steps = "How much helpful has been the step-by-step guide?"; + wae_blocks = "How much helpful has been the organization in blocks of the service?"; } } @@ -198,5 +210,31 @@ public String getTimeoutPart() { ""+ ""+ ""; - } + } + + public String getWAEPart() { + return ""+ + "
"+ + "
"+ + wae_steps + + "
"+ + "
"+ + ""+ + "
"+ slider_unuseful +"
"+ + "
"+ slider_useful +"
"+ + "
0
"+ + "
"+ + "
"+ + ""+ + "
"+ + "
"+ + wae_blocks + + "
"+ + "
"+ + ""+ + "
"+ slider_unuseful +"
"+ + "
"+ slider_useful +"
"+ + "
0
"+ + "
"+ + "
"; } From cbe8202c414d7b6a780dc779e39f6bad21463076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20M=C3=A9ndez?= Date: Wed, 18 Oct 2017 10:27:33 +0200 Subject: [PATCH 04/23] [IMP] WAE form questions if used --- .../simpatico/rest/SimpaticoResourceSF.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java index 0ec9ee8..08c1f2b 100644 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java @@ -181,7 +181,7 @@ public Response remove(@Context HttpServletRequest request, String postData) { @Path("/selectdialog") @Produces(MediaType.TEXT_HTML) public String selectDialog(@QueryParam("id") String userId, @QueryParam("ctz") Boolean ctz, @QueryParam("simpl") Boolean simpl, @QueryParam("timeout") Boolean timeout, - @QueryParam("lang") String lang) { + @QueryParam("lang") String lang, @QueryParam("wae") Boolean wae) { // Initialize the forms with the correct language Forms form = new Forms(lang); boolean wordSimp = false; @@ -261,6 +261,16 @@ public String selectDialog(@QueryParam("id") String userId, @QueryParam("ctz") B // Logger.getRootLogger().info("Free text simplification event BEFORE last session"); } } + + if (event.equals("wae")) { +// Logger.getRootLogger().info("WAE event in ES search"); + if (date.after(lastSessionDate)) { +// Logger.getRootLogger().info("WAE event AFTER last session"); + wae = true; + } else { +// Logger.getRootLogger().info("WAE event BEFORE last session"); + } + } } } catch (Exception e) { SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); @@ -273,6 +283,7 @@ public String selectDialog(@QueryParam("id") String userId, @QueryParam("ctz") B if (paragraphSimp) result += form.getParagraphSimplificationPart(); if (phraseSimp) result += form.getPhraseSimplificationPart(); if (timeout) result += form.getTimeoutPart(); + if (wae) result += form.getWAEPart(); result += form.getCommonPart(); return result; @@ -310,4 +321,4 @@ public Response testDelete(@Context HttpServletRequest request) { Logger.getLogger(FILE_LOG).warn("[TEST] IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request)); return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, "Welcome to SIMPATICO " + THIS_RESOURCE + " API! Method: DELETE"); } -} \ No newline at end of file +} From 658e2fbe7fe6a5da7e7247c1956d4ad758f3770a Mon Sep 17 00:00:00 2001 From: Jesus Gaston Date: Mon, 11 Dec 2017 23:50:37 +0000 Subject: [PATCH 05/23] [IMP] Improved KPIs. Last commit Jesus Ruben --- .../simpatico/rest/SimpaticoResourceLogs.java | 403 ++++++++++++++++- src/main/resources/simpatico.properties | 2 +- .../yaml_files/swagger-simpatico-logs.yaml | 106 ++++- .../yaml_files/swagger-simpatico-logs.yaml~ | 405 ++++++++++++++++++ 4 files changed, 909 insertions(+), 7 deletions(-) create mode 100644 src/main/webapp/dist/yaml_files/swagger-simpatico-logs.yaml~ diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceLogs.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceLogs.java index b6fcfe1..c104796 100644 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceLogs.java +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceLogs.java @@ -39,7 +39,7 @@ public class SimpaticoResourceLogs { private static String THIS_RESOURCE = "Logs"; private static int numLinesPrintStackInternalError = 1; - private static int MAX_NUM_RESULTS_REQUEST = 1000; + private static int MAX_NUM_RESULTS_REQUEST = 10000; /* KPIs */ @POST @@ -216,8 +216,8 @@ public Response reductionTimeSpent(@Context HttpServletRequest request, String p JSONObject jsonUsersWithoutSimpatico = Utils.createJSONObjectIfValid(response2.toString()); // Arrays to process data from elastic search and get how time users spent - ArrayList alUsersSimpatico = new ArrayList<>(); - ArrayList alUsersWithoutSimpatico = new ArrayList<>(); + ArrayList alUsersSimpatico = new ArrayList<>(); // ArrayList usersSimpatico + ArrayList alUsersWithoutSimpatico = new ArrayList<>(); // ArrayList usersWithoutSimpatico // Process Simpatico users JSONObject jsonUsersSimpaticoResults = jsonUsersSimpatico.getJSONObject("hits"); @@ -314,7 +314,202 @@ public Response reductionTimeSpent(@Context HttpServletRequest request, String p sup = timeSpentSimpaticoUsers - timeSpentNoSimpaticoUsers; inf = timeSpentSimpaticoUsers; } - percentage = Math.round((sup/inf * 100) * 100.0) / 100.0; + percentage = Math.round((sup/inf * 100) * 100.0) / 100.0; // Round to 2 decimals + } + + String valueReturn; + if (decrease) { + valueReturn = "-" + String.valueOf(percentage) + "%"; + } else { + valueReturn = String.valueOf(percentage) + "%"; + } + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, valueReturn); + + /*double percentage = timeSpentSimpaticoUsers / timeSpentNoSimpaticoUsers; + percentage = Math.round((percentage * 100) * 100.0) / 100.0; + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, percentage + "%");*/ + } + + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "JSON in bad format"); + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + } + + /* KPIs */ + @POST + @Path("/reduction-time-spent-all-users") + @Produces(MediaType.APPLICATION_JSON) + public Response reductionTimeSpentAll(@Context HttpServletRequest request, String postData) { + try { + // IDs user using SIMPATICO + // IDS user without using simpatico + // Date + + JSONObject json = Utils.createJSONObjectIfValid(postData); + if (json != null) { + long timeSpentNoSimpaticoUsers = 0; + long timeSpentSimpaticoUsers = 0; + long averageSpentSimpaticoUsers = 0, averageSpentNoSimpaticoUsers = 0; + int numSimpaticoUsers = 0, numNoSimpaticoUsers = 0; + + // Testing mode + Boolean testing = json.optBoolean("testing"); + if (testing) { + averageSpentSimpaticoUsers = json.optLong("averageTimeSpentUsersSimpatico", -1); + averageSpentSimpaticoUsers = json.optLong("averageTimeSpentUsersWithoutSimpatico", -1); + Logger.getRootLogger().debug("[LOGS] Testing Mode. averageTimeSpentUsersSimpatico: " + timeSpentNoSimpaticoUsers + ", averageTimeSpentUsersWithoutSimpatico: " + timeSpentSimpaticoUsers); + if (timeSpentNoSimpaticoUsers == -1 || timeSpentSimpaticoUsers == -1) { + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "JSON in bad format. averageTimeSpentUsersSimpatico and averageTimeSpentUsersWithoutSimpatico keys dont found"); + } + } else { + String dateStart = json.opt("dateStart").toString(); + String dateEnd = json.opt("dateEnd").toString(); + + + // Do request + String query1 = new String( + "{\n" + + " \"query\": {\n" + + " \"bool\" : {\n " + + " \"must\" : {\n" + + " \"range\" : {\n" + + " \"created\" : {\n" + + " \"gte\": \"" + dateStart + "\",\n" + + " \"lt\": \"" + dateEnd + "\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"must_not\" : [\n" + + " {\"match\" : { \"event\": \"elements_clicks\"} }, \n" + + " {\"match\" : { \"event\": \"session_start\"} }, \n" + + " {\"match\" : { \"event\": \"session_end\"} } \n" + + " ]\n" + + " }\n" + + " },\n" + + " \"sort\": {\n" + + " \"created\": { \"order\": \"asc\" }\n" + + " }\n" + + "}\n"); + + + URL url = new URL("http://" + SimpaticoProperties.elasticSearchIp + ":9200/shared/IFE/_search?size=" + MAX_NUM_RESULTS_REQUEST); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("Accept", "application/json"); + OutputStreamWriter osw = new OutputStreamWriter(connection.getOutputStream()); + osw.write(query1); + osw.flush(); + osw.close(); + Logger.getRootLogger().debug("[LOGS] Sending first GET to url: " + url); + Logger.getRootLogger().debug("[LOGS] Response code: " + connection.getResponseCode()); + + BufferedReader in = new BufferedReader( + new InputStreamReader(connection.getInputStream())); + String inputLine; + StringBuffer response = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + + //print result + Logger.getRootLogger().debug("[LOGS] Response: " + response.toString()); + + + // Convert each response to json and calculate percentage + JSONObject jsonUsers = Utils.createJSONObjectIfValid(response.toString()); + + // Arrays to process data from elastic search and get how time users spent + ArrayList alUsersSimpatico = new ArrayList<>(); // ArrayList usersSimpatico + ArrayList alUsersWithoutSimpatico = new ArrayList<>(); // ArrayList usersWithoutSimpatico + + // Process users + JSONObject jsonUsersResults = jsonUsers.getJSONObject("hits"); + if (jsonUsersResults.getInt("total") > 0) { + JSONObject hitSource; + String event; + String userId; + String timestamp; + //String created; + Date timeDate; + JSONArray arrayHitsUsers = jsonUsersResults.getJSONArray("hits"); + for (int i = 0; i < arrayHitsUsers.length(); i++) { + hitSource = arrayHitsUsers.getJSONObject(i).getJSONObject("_source"); + userId = hitSource.getString("userID"); + event = hitSource.getString("event"); + timestamp = hitSource.getString("timestamp"); + //created = hitSource.getString("created"); + //DateTime dateTime = ISODateTimeFormat.dateTimeParser().parseDateTime(timestamp); + //timeDate = dateTime.toDate(); + timeDate = new Date(Long.parseLong(timestamp)); + if (userId.contains("no_user_logged_")) { // User No Simpatico + if (event.equalsIgnoreCase("form_start")) { + alUsersWithoutSimpatico.add(0, new UserKPIObject(userId, timeDate)); + } else { + // Search first ocurrence with these ID and add it. If doesnt find any previous item, it seems that event form_start doesnt occurs, so we dont save + for (UserKPIObject oUser : alUsersWithoutSimpatico) { + if (oUser.getUser().equalsIgnoreCase(userId)) { + oUser.addDate(timeDate, event); + } + } + } + } else { + if (event.equalsIgnoreCase("form_start")) { + alUsersSimpatico.add(0, new UserKPIObject(userId, timeDate)); + } else { + // Search first ocurrence with these ID and add it. If doesnt find any previous item, it seems that event form_start doesnt occurs, so we dont save + for (UserKPIObject oUser : alUsersSimpatico) { + if (oUser.getUser().equalsIgnoreCase(userId)) { + oUser.addDate(timeDate, event); + } + } + } + } + } + } + + // Average time spent simpatico Users + for (UserKPIObject oUser : alUsersSimpatico) { + timeSpentSimpaticoUsers += oUser.getDiffDateMS(); + } + if (alUsersSimpatico.size() > 0) { + averageSpentSimpaticoUsers = timeSpentSimpaticoUsers / alUsersSimpatico.size(); + } + + // Average time spent NO simpatico Users + for (UserKPIObject oUser : alUsersWithoutSimpatico) { + timeSpentNoSimpaticoUsers += oUser.getDiffDateMS(); + } + if (alUsersWithoutSimpatico.size() > 0) { + averageSpentNoSimpaticoUsers = timeSpentNoSimpaticoUsers / alUsersWithoutSimpatico.size(); + } + + numSimpaticoUsers = alUsersSimpatico.size(); + numNoSimpaticoUsers = alUsersWithoutSimpatico.size(); + + } + + // Page to do percentage. Inverse because less time is better https://www.skillsyouneed.com/num/percent-change.html + double percentage; + boolean decrease = false; + if (averageSpentNoSimpaticoUsers == 0) { + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, "Infinity " + " -> average_time_spent_Simpatico: " + averageSpentSimpaticoUsers + ". average_time_spent_NoSimpatico: " + averageSpentNoSimpaticoUsers + + ". total_time_Simpatico: " + timeSpentSimpaticoUsers + " numSimpaticoUsers: " + numSimpaticoUsers + + ". total_time_NoSimpatico: " + timeSpentNoSimpaticoUsers + " numNoSimpaticoUsers: " + numNoSimpaticoUsers); + } else { + double sup = averageSpentNoSimpaticoUsers - averageSpentSimpaticoUsers; + double inf = averageSpentNoSimpaticoUsers; + if (sup < 0) { // There is a decrease + decrease = true; + sup = averageSpentSimpaticoUsers - averageSpentNoSimpaticoUsers; + inf = averageSpentSimpaticoUsers; + } + percentage = Math.round((sup/inf * 100) * 100.0) / 100.0; // Round to 2 decimals } String valueReturn; @@ -323,6 +518,9 @@ public Response reductionTimeSpent(@Context HttpServletRequest request, String p } else { valueReturn = String.valueOf(percentage) + "%"; } + valueReturn += " -> average_time_spent_Simpatico: " + averageSpentSimpaticoUsers + ". average_time_spent_NoSimpatico: " + averageSpentNoSimpaticoUsers + + ". total_time_Simpatico: " + timeSpentSimpaticoUsers + " numSimpaticoUsers: " + numSimpaticoUsers + + ". total_time_NoSimpatico: " + timeSpentNoSimpaticoUsers + " numNoSimpaticoUsers: " + numNoSimpaticoUsers; return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, valueReturn); /*double percentage = timeSpentSimpaticoUsers / timeSpentNoSimpaticoUsers; @@ -604,7 +802,7 @@ public Response percentageCompleteAutonomously(@Context HttpServletRequest reque sup = usersCompleteFormWithoutSimpatico - usersCompleteFormSimpatico; inf = usersCompleteFormWithoutSimpatico; } - percentage = Math.round((sup/inf * 100) * 100.0) / 100.0; + percentage = Math.round((sup/inf * 100) * 100.0) / 100.0; // Round to 2 decimals } String valueReturn; @@ -627,6 +825,201 @@ public Response percentageCompleteAutonomously(@Context HttpServletRequest reque return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); } } + + /* KPIs */ + @POST + @Path("/percentage-complete-autonomously-all-users") + @Produces(MediaType.APPLICATION_JSON) + public Response percentageCompleteAutonomouslyAll(@Context HttpServletRequest request, String postData) { + try { + // IDs user using SIMPATICO + // IDS user without using simpatico + // Date + + JSONObject json = Utils.createJSONObjectIfValid(postData); + if (json != null) { + int usersCompleteFormSimpatico = 0; + int usersCompleteFormWithoutSimpatico = 0; + int numSimpaticoUsers = 0, numNoSimpaticoUsers = 0; + + // Testing mode + Boolean testing = json.optBoolean("testing"); + if (testing) { + usersCompleteFormSimpatico = json.optInt("usersSimpaticoComplete", -1); + usersCompleteFormWithoutSimpatico = json.optInt("usersWithoutSimpaticoComplete", -1); + Logger.getRootLogger().debug("[LOGS] Testing Mode. usersSimpaticoComplete: " + usersCompleteFormSimpatico + ", usersWithoutSimpatico: " + usersCompleteFormWithoutSimpatico); + if (usersCompleteFormSimpatico == -1 || usersCompleteFormWithoutSimpatico == -1) { + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "JSON in bad format. usersSimpatico and usersWithoutSimpatico keys dont found"); + } + } else { + String dateStart = json.opt("dateStart").toString(); + String dateEnd = json.opt("dateEnd").toString(); + + + // Do request + String query1 = new String( + "{\n" + + " \"query\": {\n" + + " \"bool\" : {\n " + + " \"must\" : {\n" + + " \"range\" : {\n" + + " \"created\" : {\n" + + " \"gte\": \"" + dateStart + "\",\n" + + " \"lt\": \"" + dateEnd + "\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"must_not\" : [\n" + + " {\"match\" : { \"event\": \"elements_clicks\"} }, \n" + + " {\"match\" : { \"event\": \"session_start\"} }, \n" + + " {\"match\" : { \"event\": \"session_end\"} } \n" + + " ]\n" + + " }\n" + + " },\n" + + " \"sort\": {\n" + + " \"created\": { \"order\": \"asc\" }\n" + + " }\n" + + "}\n"); + + + URL url = new URL("http://" + SimpaticoProperties.elasticSearchIp + ":9200/shared/IFE/_search?size=" + MAX_NUM_RESULTS_REQUEST); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("Accept", "application/json"); + OutputStreamWriter osw = new OutputStreamWriter(connection.getOutputStream()); + osw.write(query1); + osw.flush(); + osw.close(); + Logger.getRootLogger().debug("[LOGS] Sending first GET to url: " + url); + Logger.getRootLogger().debug("[LOGS] Response code: " + connection.getResponseCode()); + + BufferedReader in = new BufferedReader( + new InputStreamReader(connection.getInputStream())); + String inputLine; + StringBuffer response = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + + //print result + Logger.getRootLogger().debug("[LOGS] Response: " + response.toString()); + + + // Convert each response to json and calculate percentage + JSONObject jsonUsers = Utils.createJSONObjectIfValid(response.toString()); + + // Arrays to process data from elastic search and get how time users spent + ArrayList alUsersSimpatico = new ArrayList<>(); // ArrayList usersSimpatico + ArrayList alUsersWithoutSimpatico = new ArrayList<>(); // ArrayList usersWithoutSimpatico + + // Process users + JSONObject jsonUsersResults = jsonUsers.getJSONObject("hits"); + if (jsonUsersResults.getInt("total") > 0) { + JSONObject hitSource; + String event; + String userId; + String timestamp; + //String created; + Date timeDate; + JSONArray arrayHitsUsers = jsonUsersResults.getJSONArray("hits"); + for (int i = 0; i < arrayHitsUsers.length(); i++) { + hitSource = arrayHitsUsers.getJSONObject(i).getJSONObject("_source"); + userId = hitSource.getString("userID"); + event = hitSource.getString("event"); + timestamp = hitSource.getString("timestamp"); + //created = hitSource.getString("created"); + //DateTime dateTime = ISODateTimeFormat.dateTimeParser().parseDateTime(timestamp); + //timeDate = dateTime.toDate(); + timeDate = new Date(Long.parseLong(timestamp)); + if (userId.contains("no_user_logged_")) { // User No Simpatico + if (event.equalsIgnoreCase("form_start")) { + alUsersWithoutSimpatico.add(0, new UserKPIObject(userId, timeDate)); + } else { + // Search first ocurrence with these ID and add it. If doesnt find any previous item, it seems that event form_start doesnt occurs, so we dont save + for (UserKPIObject oUser : alUsersWithoutSimpatico) { + if (oUser.getUser().equalsIgnoreCase(userId)) { + oUser.addDate(timeDate, event); + } + } + } + } else { + if (event.equalsIgnoreCase("form_start")) { + alUsersSimpatico.add(0, new UserKPIObject(userId, timeDate)); + } else { + // Search first ocurrence with these ID and add it. If doesnt find any previous item, it seems that event form_start doesnt occurs, so we dont save + for (UserKPIObject oUser : alUsersSimpatico) { + if (oUser.getUser().equalsIgnoreCase(userId)) { + oUser.addDate(timeDate, event); + } + } + } + } + } + } + + // Users with SIMPATICO that complete form + for (UserKPIObject oUser : alUsersSimpatico) { + if (oUser.isFormComplete()) { + usersCompleteFormSimpatico++; + } + } + + // Users without SIMPATICO that complete form + for (UserKPIObject oUser : alUsersWithoutSimpatico) { + if (oUser.isFormComplete()) { + usersCompleteFormWithoutSimpatico++; + } + } + + numSimpaticoUsers = alUsersSimpatico.size(); + numNoSimpaticoUsers = alUsersWithoutSimpatico.size(); + } + + // Page to do percentage https://www.skillsyouneed.com/num/percent-change.html + double percentage; + boolean decrease = false; + if (usersCompleteFormWithoutSimpatico == 0) { + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, "Infinity -> " + + ". NumSimpaticoUsersComplete: " + usersCompleteFormSimpatico + ". NumSimpaticoUsers: " + numSimpaticoUsers + + ". NumNoSimpaticoUsersComplete: " + usersCompleteFormWithoutSimpatico + " NumNoSimpaticoUsers: " + numNoSimpaticoUsers); + } else { + double sup = usersCompleteFormSimpatico - usersCompleteFormWithoutSimpatico; + double inf = usersCompleteFormWithoutSimpatico; + if (sup < 0) { // There is a decrease + decrease = true; + sup = usersCompleteFormWithoutSimpatico - usersCompleteFormSimpatico; + inf = usersCompleteFormWithoutSimpatico; + } + percentage = Math.round((sup/inf * 100) * 100.0) / 100.0; // Round to 2 decimals + } + + String valueReturn; + if (decrease) { + valueReturn = "-" + String.valueOf(percentage) + "%"; + } else { + valueReturn = String.valueOf(percentage) + "%"; + } + valueReturn += " -> NumSimpaticoUsersComplete: " + usersCompleteFormSimpatico + ". NumSimpaticoUsers: " + numSimpaticoUsers + + ". NumNoSimpaticoUsersComplete: " + usersCompleteFormWithoutSimpatico + " NumNoSimpaticoUsers: " + numNoSimpaticoUsers; + + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, valueReturn); + + + /*double percentage = timeSpentSimpaticoUsers / timeSpentNoSimpaticoUsers; + percentage = Math.round((percentage * 100) * 100.0) / 100.0; + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, percentage + "%");*/ + } + + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "JSON in bad format"); + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + } @GET @Path("/find/") diff --git a/src/main/resources/simpatico.properties b/src/main/resources/simpatico.properties index ee1c023..c306f14 100644 --- a/src/main/resources/simpatico.properties +++ b/src/main/resources/simpatico.properties @@ -12,7 +12,7 @@ elasticsearch.piwik.type=piwikAnalytics # Piwik piwik.api_url=http://127.0.0.1:90/piwik/?module=API -piwik.auth_token= +piwik.auth_token=635b8c4939d08a031ee40acbc8626921 # Name created field (timestamp) elasticsearch.created.field.name=created diff --git a/src/main/webapp/dist/yaml_files/swagger-simpatico-logs.yaml b/src/main/webapp/dist/yaml_files/swagger-simpatico-logs.yaml index eed2d68..fed7942 100644 --- a/src/main/webapp/dist/yaml_files/swagger-simpatico-logs.yaml +++ b/src/main/webapp/dist/yaml_files/swagger-simpatico-logs.yaml @@ -10,6 +10,34 @@ schemes: produces: - "application/json" paths: + /reduction-time-spent-all-users: + post: + tags: + - "Time spent" + summary: "Time spent completing form" + description: "Get increase or decrease percentage between time spent by all users using Simpatico and those who do not use Simpatico.\nDates in UTC String" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "query" + description: "A valid JSON to insert." + required: true + schema: + $ref: "#/definitions/Message_KPI_All_Users_Time" + responses: + 200: + description: "A percentage or Infinity." + schema: + $ref: "#/definitions/Message" + 400: + description: "Bad request." + 500: + description: "Unexpected error." + schema: + $ref: "#/definitions/Message" /reduction-time-spent: post: tags: @@ -65,7 +93,35 @@ paths: 500: description: "Unexpected error." schema: - $ref: "#/definitions/Message" + $ref: "#/definitions/Message" + /percentage-complete-autonomously-all-users: + post: + tags: + - "Complete autonomously" + summary: "Time spent completing form" + description: "Get increase or decrease percentage between users that fill out completely the form using Simpatico and those who do not use Simpatico.\n" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "query" + description: "A valid JSON to insert." + required: true + schema: + $ref: "#/definitions/Message_KPI_All_Users_Complete_Form" + responses: + 200: + description: "A percentage or Infinity." + schema: + $ref: "#/definitions/Message" + 400: + description: "Bad request." + 500: + description: "Unexpected error." + schema: + $ref: "#/definitions/Message" /find: get: tags: @@ -262,12 +318,15 @@ definitions: $ref: "#/definitions/Array_Users" dateStart: type: "string" + example: "2017-10-20T08:00:00Z" description: "Date in UTC" dateEnd: type: "string" + example: "2017-10-25T00:00:00Z" description: "Date in UTC" testing: type: "boolean" + example: false description: "If true, it use values in keys averageTimeSpentUsersSimpatico and averageTimeSpentUsersWithoutSimpatico" averageTimeSpentUsersSimpatico: type: "integer" @@ -275,6 +334,27 @@ definitions: averageTimeSpentUsersWithoutSimpatico: type: "integer" description: "Average time spent by users without using Simpatico" + Message_KPI_All_Users_Time: + type: "object" + properties: + dateStart: + type: "string" + example: "2017-10-20T00:00:00Z" + description: "Date in UTC" + dateEnd: + type: "string" + example: "2017-10-25T00:00:00Z" + description: "Date in UTC" + testing: + type: "boolean" + example: false + description: "If true, it use values in keys averageTimeSpentUsersSimpatico and averageTimeSpentUsersWithoutSimpatico" + averageTimeSpentUsersSimpatico: + type: "integer" + description: "Average time spent by users using Simpatico" + averageTimeSpentUsersWithoutSimpatico: + type: "integer" + description: "Average time spent by users without using Simpatico" Message_KPI_Complete_Form: type: "object" properties: @@ -288,12 +368,15 @@ definitions: $ref: "#/definitions/Array_Users" dateStart: type: "string" + example: "2017-10-20T00:00:00Z" description: "Date in UTC" dateEnd: type: "string" + example: "2017-10-25T00:00:00Z" description: "Date in UTC" testing: type: "boolean" + example: false description: "If true, it use values in keys usersSimpaticoComplete and usersWithoutSimpaticoComplete" usersSimpaticoComplete: type: "integer" @@ -301,6 +384,27 @@ definitions: usersWithoutSimpaticoComplete: type: "integer" description: "Users array to check if them complete the form" + Message_KPI_All_Users_Complete_Form: + type: "object" + properties: + dateStart: + type: "string" + example: "2017-10-20T00:00:00Z" + description: "Date in UTC" + dateEnd: + type: "string" + example: "2017-10-25T00:00:00Z" + description: "Date in UTC" + testing: + type: "boolean" + example: false + description: "If true, it use values in keys usersSimpaticoComplete and usersWithoutSimpaticoComplete" + usersSimpaticoComplete: + type: "integer" + description: "Users array to check if them complete the form" + usersWithoutSimpaticoComplete: + type: "integer" + description: "Users array to check if them complete the form" inline_response_200: properties: count: diff --git a/src/main/webapp/dist/yaml_files/swagger-simpatico-logs.yaml~ b/src/main/webapp/dist/yaml_files/swagger-simpatico-logs.yaml~ new file mode 100644 index 0000000..3ea6cd2 --- /dev/null +++ b/src/main/webapp/dist/yaml_files/swagger-simpatico-logs.yaml~ @@ -0,0 +1,405 @@ +--- +swagger: "2.0" +info: + version: "1.0.0" + title: "Simpatico Logs API" +host: "simpatico.hi-iberia.es:4570" +basePath: "/simpatico/api/logs" +schemes: +- "https" +produces: +- "application/json" +paths: + /reduction-time-spent-all-users: + post: + tags: + - "Time spent" + summary: "Time spent completing form" + description: "Get increase or decrease percentage between time spent by all users using Simpatico and those who do not use Simpatico.\nDates in UTC String" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "query" + description: "A valid JSON to insert." + required: true + schema: + $ref: "#/definitions/Message_KPI_All_Users_Time" + responses: + 200: + description: "A percentage or Infinity." + schema: + $ref: "#/definitions/Message" + 400: + description: "Bad request." + 500: + description: "Unexpected error." + schema: + $ref: "#/definitions/Message" + /reduction-time-spent: + post: + tags: + - "Time spent" + summary: "Time spent completing form" + description: "Get increase or decrease percentage between time spent by users using Simpatico and those who do not use Simpatico.\nDates in UTC String" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "query" + description: "A valid JSON to insert." + required: true + schema: + $ref: "#/definitions/Message_KPI_Time" + responses: + 200: + description: "A percentage or Infinity." + schema: + $ref: "#/definitions/Message" + 400: + description: "Bad request." + 500: + description: "Unexpected error." + schema: + $ref: "#/definitions/Message" + /percentage-complete-autonomously: + post: + tags: + - "Complete autonomously" + summary: "Time spent completing form" + description: "Get increase or decrease percentage between users that fill out completely the form using Simpatico and those who do not use Simpatico.\n" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "query" + description: "A valid JSON to insert." + required: true + schema: + $ref: "#/definitions/Message_KPI_Complete_Form" + responses: + 200: + description: "A percentage or Infinity." + schema: + $ref: "#/definitions/Message" + 400: + description: "Bad request." + 500: + description: "Unexpected error." + schema: + $ref: "#/definitions/Message" + /percentage-complete-autonomously-all-users: + post: + tags: + - "Complete autonomously" + summary: "Time spent completing form" + description: "Get increase or decrease percentage between users that fill out completely the form using Simpatico and those who do not use Simpatico.\n" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "query" + description: "A valid JSON to insert." + required: true + schema: + $ref: "#/definitions/Message_KPI_All_Users_Complete_Form" + responses: + 200: + description: "A percentage or Infinity." + schema: + $ref: "#/definitions/Message" + 400: + description: "Bad request." + 500: + description: "Unexpected error." + schema: + $ref: "#/definitions/Message" + /find: + get: + tags: + - "find" + summary: "Find documents" + description: "Find documents by words.\n" + produces: + - "application/json" + parameters: + - name: "words" + in: "query" + description: "Comma separated words. Espaces are allowed." + required: false + type: "string" + - name: "sortasc" + in: "query" + description: "Created time ascending sort." + required: false + type: "string" + - name: "sortdesc" + in: "query" + description: "Created time descending sort." + required: false + type: "string" + - name: "limit" + in: "query" + description: "Limit the number of results." + required: false + type: "number" + format: "integer" + responses: + 200: + description: "An array of documents and how many are." + schema: + $ref: "#/definitions/inline_response_200" + 500: + description: "Unexpected error." + schema: + $ref: "#/definitions/Message" + /insert: + post: + tags: + - "insert" + summary: "Insert one document" + description: "Insert one document. If in JSON there are '_id' key, the document will insert with these id.\n" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "query" + description: "A valid JSON to insert." + required: true + schema: + type: "object" + responses: + 200: + description: "A message information that data was updated." + schema: + $ref: "#/definitions/Message" + 201: + description: "A message information that data was inserted." + schema: + $ref: "#/definitions/Message" + 400: + description: "Bad request." + 500: + description: "Unexpected error." + schema: + $ref: "#/definitions/Message" + /remove: + delete: + tags: + - "delete" + summary: "Delete one document" + description: "Delete one document. JSON with 'id' key is required.\n" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "query" + description: "A valid JSON with 'id' key." + required: true + schema: + $ref: "#/definitions/Document_Delete" + responses: + 200: + description: "A message information that data was deleted." + schema: + $ref: "#/definitions/Message" + 400: + description: "Bad request. JSON invalid." + 500: + description: "Unexpected error." + schema: + $ref: "#/definitions/Message" + /update: + put: + tags: + - "update" + summary: "Update one document" + description: "Update document's fields. JSON with 'id' and 'content' keys is\ + \ required. If the field does not exist, it is added (preserving all previous\ + \ ones). \n" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "query" + description: "A valid JSON with 'id' and 'content' keys," + required: true + schema: + $ref: "#/definitions/Document_Update" + responses: + 200: + description: "A message information that data was updated." + schema: + $ref: "#/definitions/Message" + 201: + description: "If document does not exists, It will be inserted." + schema: + $ref: "#/definitions/Message" + 400: + description: "Bad request. JSON invalid." + 500: + description: "Unexpected error." + schema: + $ref: "#/definitions/Message" + /test: + get: + tags: + - "test" + description: "" + produces: + - "application/json" + parameters: [] + responses: + 200: + description: "A message to check available API." + schema: + $ref: "#/definitions/Message" +definitions: + Document: + type: "object" + properties: + id: + type: "string" + description: "Unique identifier representing a specific document." + score: + type: "number" + format: "float" + description: "Relevancy in finding." + data: + type: "string" + description: "document data." + Document_Update: + type: "object" + properties: + id: + type: "string" + description: "Unique identifier representing a specific document." + content: + type: "object" + description: "Valid json." + properties: {} + Document_Delete: + type: "object" + properties: + id: + type: "string" + description: "Unique identifier representing a specific document." + Array_Users: + type: "string" + Message: + type: "object" + properties: + message: + type: "string" + Message_KPI_Time: + type: "object" + properties: + usersSimpatico: + type: "array" + items: + $ref: "#/definitions/Array_Users" + usersWithoutSimpatico: + type: "array" + items: + $ref: "#/definitions/Array_Users" + dateStart: + type: "string" + description: "Date in UTC" + dateEnd: + type: "string" + description: "Date in UTC" + testing: + type: "boolean" + description: "If true, it use values in keys averageTimeSpentUsersSimpatico and averageTimeSpentUsersWithoutSimpatico" + averageTimeSpentUsersSimpatico: + type: "integer" + description: "Average time spent by users using Simpatico" + averageTimeSpentUsersWithoutSimpatico: + type: "integer" + description: "Average time spent by users without using Simpatico" + Message_KPI_All_Users_Time: + type: "object" + properties: + dateStart: + type: "string" + description: "Date in UTC" + dateEnd: + type: "string" + description: "Date in UTC" + testing: + type: "boolean" + description: "If true, it use values in keys averageTimeSpentUsersSimpatico and averageTimeSpentUsersWithoutSimpatico" + averageTimeSpentUsersSimpatico: + type: "integer" + description: "Average time spent by users using Simpatico" + averageTimeSpentUsersWithoutSimpatico: + type: "integer" + description: "Average time spent by users without using Simpatico" + Message_KPI_Complete_Form: + type: "object" + properties: + usersSimpatico: + type: "array" + items: + $ref: "#/definitions/Array_Users" + usersWithoutSimpatico: + type: "array" + items: + $ref: "#/definitions/Array_Users" + dateStart: + type: "string" + description: "Date in UTC" + dateEnd: + type: "string" + description: "Date in UTC" + testing: + type: "boolean" + description: "If true, it use values in keys usersSimpaticoComplete and usersWithoutSimpaticoComplete" + usersSimpaticoComplete: + type: "integer" + description: "Users array to check if them complete the form" + usersWithoutSimpaticoComplete: + type: "integer" + description: "Users array to check if them complete the form" + Message_KPI_All_Users_Complete_Form: + type: "object" + properties: + dateStart: + type: "string" + description: "Date in UTC" + dateEnd: + type: "string" + description: "Date in UTC" + testing: + type: "boolean" + description: "If true, it use values in keys usersSimpaticoComplete and usersWithoutSimpaticoComplete" + usersSimpaticoComplete: + type: "integer" + description: "Users array to check if them complete the form" + usersWithoutSimpaticoComplete: + type: "integer" + description: "Users array to check if them complete the form" + inline_response_200: + properties: + count: + type: "number" + format: "int" + description: "Unique identifier representing a specific document." + results: + type: "array" + items: + $ref: "#/definitions/Document" From 94737bea620d7784ed14678d5656b70e90717597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Garc=C3=ADa?= Date: Wed, 31 Jan 2018 14:48:07 +0100 Subject: [PATCH 06/23] [ADD] Endpoints for new dashboard (eSM) --- .gitignore | 1 + .../simpatico/rest/SimpaticoResourceIFE.java | 5 +- .../simpatico/rest/SimpaticoResourceLogs.java | 1 + .../simpatico/rest/SimpaticoResourceSF.java | 65 ++++++++- .../rest/SimpaticoResourceSearchByFields.java | 65 +++++++++ .../rest/SimpaticoResourceUtils.java | 125 +++++++++++++++++- .../utils/ElasticSearchConnector.java | 68 +++++++++- .../es/hiiberia/simpatico/utils/Forms.java | 1 + src/main/resources/simpatico.properties | 2 +- 9 files changed, 318 insertions(+), 15 deletions(-) create mode 100644 src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSearchByFields.java diff --git a/.gitignore b/.gitignore index 12c482e..48bb686 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ deploymentWARSimpatico.sh .settings/* .project .classpath +.externalToolBuilders/ diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceIFE.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceIFE.java index 81eb6a0..57765d1 100644 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceIFE.java +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceIFE.java @@ -86,8 +86,8 @@ public Response find_sessionstart(@Context HttpServletRequest request, @Context queryParams.put(SimpaticoResourceUtils.wordsParam, words); } else { words.add(EVENT_SESSION_START); - } - + } + return SimpaticoResourceUtils.findRequest(request, queryParams, ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); } catch (Exception e) { SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); @@ -287,6 +287,7 @@ public Response insert_form_start(@Context HttpServletRequest request, String po try { // Check parameters and generate event attribute JSONObject jsonObject = Utils.createJSONObjectIfValid(postData); + if (jsonObject != null) { if (jsonObject.has(USER_ID) && jsonObject.has(E_SERVICE_ID) && jsonObject.has(FORM_ID) && jsonObject.has(TIMESTAMP)) { diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceLogs.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceLogs.java index c104796..f6f26f7 100644 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceLogs.java +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceLogs.java @@ -1025,6 +1025,7 @@ public Response percentageCompleteAutonomouslyAll(@Context HttpServletRequest re @Path("/find/") @Produces(MediaType.APPLICATION_JSON) public Response find(@Context HttpServletRequest request, @Context UriInfo uriInfo) { + try { return SimpaticoResourceUtils.findRequest(request, uriInfo, ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); } catch (Exception e) { diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java index 08c1f2b..771c087 100644 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java @@ -79,6 +79,68 @@ public Response find_sf(@Context HttpServletRequest request, @Context UriInfo ur } } + + + + @GET + @Path("/find2") + @Produces(MediaType.APPLICATION_JSON) + public Response findAnyField_sf(@Context HttpServletRequest request, @Context UriInfo uriInfo) { + + /*try { + // Copy map (it is unmodificable) + Map> queryParamsUnmodificable = uriInfo.getQueryParameters(); + Map> queryParams = new MultivaluedHashMap<>(); + + if (queryParamsUnmodificable != null) + queryParams.putAll(queryParamsUnmodificable); + + List words = queryParams.get("search"); + if (words == null) { + words = new ArrayList<>(); + words.add(EVENT_SESSION_FEEDBACK); + queryParams.put(SimpaticoResourceUtils.wordsParam, words); + } else { + words.add(EVENT_SESSION_FEEDBACK); + } + + return SimpaticoResourceUtils.findRequest(request, queryParams, ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + }*/ + + try { + return SimpaticoResourceUtils.findRequest(request, uriInfo, ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + + + + + + } + + + + + + + + + + + + + + + + + + + /** * Insert a json document. The postData must be a valid json (we store the full json) * @param request @@ -181,12 +243,13 @@ public Response remove(@Context HttpServletRequest request, String postData) { @Path("/selectdialog") @Produces(MediaType.TEXT_HTML) public String selectDialog(@QueryParam("id") String userId, @QueryParam("ctz") Boolean ctz, @QueryParam("simpl") Boolean simpl, @QueryParam("timeout") Boolean timeout, - @QueryParam("lang") String lang, @QueryParam("wae") Boolean wae) { + @QueryParam("lang") String lang) { // Initialize the forms with the correct language Forms form = new Forms(lang); boolean wordSimp = false; boolean phraseSimp = false; boolean paragraphSimp = false; + boolean wae = false; // Search in ES what the user has done since he started the last session try { diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSearchByFields.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSearchByFields.java new file mode 100644 index 0000000..7a9005c --- /dev/null +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSearchByFields.java @@ -0,0 +1,65 @@ +package es.hiiberia.simpatico.rest; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import es.hiiberia.simpatico.utils.SimpaticoProperties; + +@Path("/search") +public class SimpaticoResourceSearchByFields { + private String searchParam= "search"; + private static String ES_INDEX = SimpaticoProperties.elasticSearchHIIndex; + private static String ES_TYPE = "SF"; + private static String ES_FIELD_SEARCH = SimpaticoProperties.elasticSearchFieldSearch; + private static String FILE_LOG = SimpaticoProperties.simpaticoLog_Logs; + private static String THIS_RESOURCE = "SF"; + private static int numLinesPrintStackInternalError = 1; + + + + + @GET + @Path("/find") + @Produces(MediaType.APPLICATION_JSON) + public Response find_sbf(@Context HttpServletRequest request, @Context UriInfo uriInfo) { + + try { + // Copy map (it is unmodificable) + Map> queryParamsUnmodificable = uriInfo.getQueryParameters(); + Map> queryParams = new MultivaluedHashMap<>(); + + if (queryParamsUnmodificable != null) + queryParams.putAll(queryParamsUnmodificable); + + + //List words = queryParams.get(searchParam); + /*if (words == null) { + words = new ArrayList<>(); + //words.add(EVENT_SESSION_FEEDBACK); + queryParams.put(SimpaticoResourceUtils.wordsParam, words); + } else { + words.add(EVENT_SESSION_FEEDBACK); + }*/ + + return SimpaticoResourceUtils.findRequest(request, queryParams, ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + + + } + + +} diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceUtils.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceUtils.java index 6374a3a..f44b30c 100644 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceUtils.java +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceUtils.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -119,12 +120,13 @@ public static void logException (Exception e, String FILE_LOG, String THIS_RESOU public static Response findRequest(HttpServletRequest request, Map> queryParams, String ES_INDEX, String ES_TYPE, String ES_FIELD_SEARCH, String FILE_LOG, String THIS_RESOURCE) throws IOException, Exception { ArrayList literalWords = new ArrayList<>(); + String common_key= null; int limit = 0; String fieldSortName = ""; SortOrder sortOrder = SortOrder.ASC; // Inicialize. If fieldSort is empty dont sort Logger.getLogger(FILE_LOG).info("Find documents. IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request) + ". Query: " + queryParams.toString()); - + // Process query params for (Map.Entry> entry : queryParams.entrySet()) { String key = entry.getKey(); @@ -138,6 +140,22 @@ public static Response findRequest(HttpServletRequest request, Map return full documents stored if (literalWords.isEmpty()) { responseES = ElasticSearchConnector.getInstance().search(ES_INDEX, ES_TYPE, fieldSortName, sortOrder, limit); - } else { + + // Last word of literalWords indicates us search by any field + }else if(literalWords.get(literalWords.size()-1).equals("searchByField")){ + literalWords.remove(literalWords.size()-1); + responseES = ElasticSearchConnector.getInstance().searchByAnyField(ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, common_key, literalWords, fieldSortName, sortOrder, limit); + + }else { responseES = ElasticSearchConnector.getInstance().search(ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, literalWords, fieldSortName, sortOrder, limit); } @@ -172,6 +196,8 @@ public static Response findRequest(HttpServletRequest request, UriInfo uriInfo, ArrayList literalWords = new ArrayList<>(); int limit = 0; + String common_key= null; + String exist= null; String fieldSortName = ""; String fieldSearch = ""; SortOrder sortOrder = SortOrder.ASC; // Inicialize. If fieldSort is empty dont sort @@ -193,8 +219,26 @@ public static Response findRequest(HttpServletRequest request, UriInfo uriInfo, literalWords.add(splitWord); } } + //Store common_key + } else if(key.contentEquals("common")){ + common_key= values.get(0); + + } else if(key.contentEquals("exist")){ + exist= values.get(0); + + // Search + } else if(key.contentEquals("search")){ + for (String word : values) { + // Comma separated and add to array + for (String splitWord : word.split(SimpaticoResourceUtils.separateParam)) { + literalWords.add(splitWord); + } + } + + literalWords.add("searchByField"); + // Limit - } else if (key.contentEquals(SimpaticoResourceUtils.limitParam)) { + }else if (key.contentEquals(SimpaticoResourceUtils.limitParam)) { if (!values.isEmpty() && Utils.isInteger(values.get(0))) { limit = Integer.parseInt(values.get(0)); } @@ -219,19 +263,82 @@ public static Response findRequest(HttpServletRequest request, UriInfo uriInfo, } SearchResponse responseES; + + + if((common_key!=null)&&(!fieldSearch.equals(""))){ + responseES = ElasticSearchConnector.getInstance().searchByKey_Value(ES_INDEX, ES_TYPE, common_key, fieldSearch, literalWords, fieldSortName, sortOrder, limit); + // No params, so empty request -> return full documents stored - if (!fieldSearch.isEmpty()) { + } else if (!fieldSearch.isEmpty()) { //Logger.getRootLogger().info("[RESOURCE_UTILS] FINDING with field"); responseES = ElasticSearchConnector.getInstance().search(ES_INDEX, ES_TYPE, fieldSearch, literalWords, fieldSortName, sortOrder, limit); - } else if (literalWords.isEmpty()) { + + //Calculate how many documents exist with this field. + } else if (exist!=null) { + responseES = ElasticSearchConnector.getInstance().search_Exist(ES_INDEX, ES_TYPE, exist, fieldSortName, sortOrder, limit); + + } else if (literalWords.isEmpty()) { //Logger.getRootLogger().info("[RESOURCE_UTILS] NOP"); responseES = ElasticSearchConnector.getInstance().search(ES_INDEX, ES_TYPE, fieldSortName, sortOrder, limit); - } else { + + // Last word of literalWords indicates us search by any field with common key + } else if(literalWords.get(literalWords.size()-1).equals("searchByField")) { + literalWords.remove(literalWords.size()-1); + responseES = ElasticSearchConnector.getInstance().searchByAnyField(ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, common_key, literalWords, fieldSortName, sortOrder, limit); + + } else { //Logger.getRootLogger().info("[RESOURCE_UTILS] NOP"); responseES = ElasticSearchConnector.getInstance().search(ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, literalWords, fieldSortName, sortOrder, limit); } - return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.searchResponse2JSONResponse(responseES)); + //Filter the result when it's one of two. + if(fieldSearch.equals("slider_session_feedback_ctz")||fieldSearch.equals("slider_session_feedback_paragraph")){ + + Response query= SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.searchResponse2JSONResponse(responseES)); + JSONArray JsonArray_aux= new JSONArray(); + JSONObject jsonObject_final= new JSONObject(); + JSONObject jsonObject_data; + + Logger.getRootLogger().info(query.getEntity().toString()); + JSONObject jsonObject = Utils.createJSONObjectIfValid(query.getEntity().toString()); + + //Get an array with all JSONObject + JSONArray JSON_arr= jsonObject.getJSONArray("results"); + + //Create a new JSONArray with values we want + for(int i=0; i< JSON_arr.length();i++){ + JSONObject jsonObject2= (JSONObject) JSON_arr.get(i); + jsonObject_data= (JSONObject)jsonObject2.get("data"); + + int valor= (int) jsonObject_data.get(fieldSearch); + if(literalWords.contains(Integer.toString(valor))){ + JsonArray_aux.put((JSONObject) JSON_arr.get(i)); + } + } + + //Create a definitive JSONObject with values updates Create a document with updated values + + Iterator it= jsonObject.keys(); + while(it.hasNext()){ + String key= (String)it.next(); + if(key.equals("count")){ + jsonObject_final.put(key, JsonArray_aux.length()); + + } else if(key.equals("results")){ + jsonObject_final.put(key, JsonArray_aux); + + } else{ + jsonObject_final.put(key, jsonObject.get(key)); + + } + } + + return SimpaticoResourceUtils.createMessageResponse(jsonObject_final); + + } else { + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.searchResponse2JSONResponse(responseES)); + + } } public static Response insertRequest(HttpServletRequest request, String postData, String ES_INDEX, String ES_TYPE, String ES_FIELD_SEARCH, String FILE_LOG, String THIS_RESOURCE) throws Exception { @@ -240,6 +347,7 @@ public static Response insertRequest(HttpServletRequest request, String postData IndexResponse responseInsert; Response response; + JSONObject jsonObject = Utils.createJSONObjectIfValid(postData); if (jsonObject != null) { Logger.getLogger(FILE_LOG).info("Insert document. IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request) + ". POST data: " + postData); // Converted in Utils.createJSONStringIfValid @@ -252,13 +360,16 @@ public static Response insertRequest(HttpServletRequest request, String postData connector.createIndexWithDateField(ES_INDEX, ES_TYPE, SimpaticoProperties.elasticSearchCreatedFieldName); } + // Add created time in utc jsonObject.put(SimpaticoProperties.elasticSearchCreatedFieldName, new DateTime(new Date()).withZone(DateTimeZone.UTC).toString("yyyy-MM-dd'T'HH:mm:ss'Z'")); + // Check if "_id" param inside if (jsonObject.has(SimpaticoResourceUtils._idParam)) { id = jsonObject.getString(SimpaticoResourceUtils._idParam); jsonObject.remove(SimpaticoResourceUtils._idParam); + // Insert data with id responseInsert = connector.insertDocument(ES_INDEX, ES_TYPE, id, jsonObject.toString()); } else { diff --git a/src/main/java/es/hiiberia/simpatico/utils/ElasticSearchConnector.java b/src/main/java/es/hiiberia/simpatico/utils/ElasticSearchConnector.java index d4c2800..ffa751b 100644 --- a/src/main/java/es/hiiberia/simpatico/utils/ElasticSearchConnector.java +++ b/src/main/java/es/hiiberia/simpatico/utils/ElasticSearchConnector.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.net.InetAddress; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -21,6 +22,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.MultiMatchQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.internal.InternalSearchResponse; @@ -184,8 +186,7 @@ public SearchResponse search(String index, int limit) throws IOException { } public SearchResponse search(String index, String type, String field, List words, String fieldSort, SortOrder ord, int limit) throws IOException { - - BoolQueryBuilder boolQuery = new BoolQueryBuilder(); + BoolQueryBuilder boolQuery = new BoolQueryBuilder(); for (String word : words) { boolQuery.should(QueryBuilders.matchQuery(field, word)); } @@ -205,6 +206,64 @@ public SearchResponse searchByField(String index, String type, String field, Lis return searchES (index, type, boolQuery, fieldSort, ord, limit); } + /** + *Method that searches for all the words that have de common key + * @param common_key + * @param words: array with all values to search + * @param fieldSort: _all + * @throws IOException + */ + + public SearchResponse searchByAnyField(String index, String type, String field, String common_key, List words, String fieldSort, SortOrder ord, int limit) throws IOException { + BoolQueryBuilder boolQuery = new BoolQueryBuilder(); + BoolQueryBuilder boolQuery2 = new BoolQueryBuilder(); + for (String word : words) { + boolQuery2.should(QueryBuilders.matchQuery(field, word)); + } + boolQuery.must(QueryBuilders.matchQuery(field, common_key)).must(boolQuery2); + + return searchES (index, type, boolQuery, fieldSort, ord, limit); + } + + + /** + * Search all documents with match between field: word and also have common_key. + * @param common_key + * @param field: name of field + * @param words: have to match with field + * @throws IOException + */ + public SearchResponse searchByKey_Value(String index, String type, String common_key, String field, List words, String fieldSort, SortOrder ord, int limit) throws IOException { + //Normally common_key is e-service + String field_common_key= "_all"; + BoolQueryBuilder boolQuery = new BoolQueryBuilder(); + BoolQueryBuilder boolQuery2 = new BoolQueryBuilder(); + for (String word : words) { + boolQuery2.should(QueryBuilders.matchQuery(field, word)); + } + + boolQuery.must(QueryBuilders.matchQuery(field_common_key, common_key)).must(boolQuery2); + + return searchES (index, type, boolQuery, fieldSort, ord, limit); + + + } + +/** + * Calculates number of document with name of field= exist. + * @param exist: name of field to search. + * @throws IOException + */ + public SearchResponse search_Exist(String index, String type, String exist, String fieldSort, SortOrder ord, int limit) throws IOException { + BoolQueryBuilder boolQuery = new BoolQueryBuilder(); + boolQuery.must(QueryBuilders.existsQuery(exist)); + return searchES (index, type, boolQuery, fieldSort, ord, limit); + + + } + + + private SearchResponse searchES (String index, String type, QueryBuilder qb, String fieldSort, SortOrder ord, int limit) throws IOException { try { @@ -214,7 +273,7 @@ private SearchResponse searchES (String index, String type, QueryBuilder qb, Str // Add query if (qb != null) { - searchRequest.setQuery(qb); + searchRequest.setQuery(qb); } else { searchRequest.setQuery(QueryBuilders.matchAllQuery()); } @@ -237,7 +296,8 @@ private SearchResponse searchES (String index, String type, QueryBuilder qb, Str } SearchResponse response = searchRequest.get(); - + + return response; } catch (IndexNotFoundException e) { Logger.getRootLogger().warn("Elastic search: Searching and there are not index created previusly (index: " + index + ")"); diff --git a/src/main/java/es/hiiberia/simpatico/utils/Forms.java b/src/main/java/es/hiiberia/simpatico/utils/Forms.java index 739fa8d..82d5bb5 100644 --- a/src/main/java/es/hiiberia/simpatico/utils/Forms.java +++ b/src/main/java/es/hiiberia/simpatico/utils/Forms.java @@ -237,4 +237,5 @@ public String getWAEPart() { "
0
"+ ""+ ""; + } } diff --git a/src/main/resources/simpatico.properties b/src/main/resources/simpatico.properties index c306f14..1d06695 100644 --- a/src/main/resources/simpatico.properties +++ b/src/main/resources/simpatico.properties @@ -1,7 +1,7 @@ # Simpatico # Elastic search config -elasticsearch.ip=localhost +elasticsearch.ip=192.168.26.71 elasticsearch.port=9300 elasticsearch.clustername=simpatico From 848d815a09d7ee3f336ecc256a17f5306f571c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Garc=C3=ADa?= Date: Fri, 2 Feb 2018 10:52:17 +0100 Subject: [PATCH 07/23] [IMP] removed comments and search_Exist filtered by e-service --- .../simpatico/rest/SimpaticoResourceSF.java | 62 ----- .../rest/SimpaticoResourceSearchByFields.java | 65 ----- .../rest/SimpaticoResourceTwitter.java | 225 ++++++++++++++++++ .../rest/SimpaticoResourceUtils.java | 23 +- .../utils/ElasticSearchConnector.java | 66 +++-- 5 files changed, 274 insertions(+), 167 deletions(-) delete mode 100644 src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSearchByFields.java create mode 100644 src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceTwitter.java diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java index 771c087..d7fb69d 100644 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java @@ -79,68 +79,6 @@ public Response find_sf(@Context HttpServletRequest request, @Context UriInfo ur } } - - - - @GET - @Path("/find2") - @Produces(MediaType.APPLICATION_JSON) - public Response findAnyField_sf(@Context HttpServletRequest request, @Context UriInfo uriInfo) { - - /*try { - // Copy map (it is unmodificable) - Map> queryParamsUnmodificable = uriInfo.getQueryParameters(); - Map> queryParams = new MultivaluedHashMap<>(); - - if (queryParamsUnmodificable != null) - queryParams.putAll(queryParamsUnmodificable); - - List words = queryParams.get("search"); - if (words == null) { - words = new ArrayList<>(); - words.add(EVENT_SESSION_FEEDBACK); - queryParams.put(SimpaticoResourceUtils.wordsParam, words); - } else { - words.add(EVENT_SESSION_FEEDBACK); - } - - return SimpaticoResourceUtils.findRequest(request, queryParams, ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); - } catch (Exception e) { - SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); - return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); - }*/ - - try { - return SimpaticoResourceUtils.findRequest(request, uriInfo, ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); - } catch (Exception e) { - SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); - return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); - } - - - - - - } - - - - - - - - - - - - - - - - - - - /** * Insert a json document. The postData must be a valid json (we store the full json) * @param request diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSearchByFields.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSearchByFields.java deleted file mode 100644 index 7a9005c..0000000 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSearchByFields.java +++ /dev/null @@ -1,65 +0,0 @@ -package es.hiiberia.simpatico.rest; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedHashMap; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; - -import es.hiiberia.simpatico.utils.SimpaticoProperties; - -@Path("/search") -public class SimpaticoResourceSearchByFields { - private String searchParam= "search"; - private static String ES_INDEX = SimpaticoProperties.elasticSearchHIIndex; - private static String ES_TYPE = "SF"; - private static String ES_FIELD_SEARCH = SimpaticoProperties.elasticSearchFieldSearch; - private static String FILE_LOG = SimpaticoProperties.simpaticoLog_Logs; - private static String THIS_RESOURCE = "SF"; - private static int numLinesPrintStackInternalError = 1; - - - - - @GET - @Path("/find") - @Produces(MediaType.APPLICATION_JSON) - public Response find_sbf(@Context HttpServletRequest request, @Context UriInfo uriInfo) { - - try { - // Copy map (it is unmodificable) - Map> queryParamsUnmodificable = uriInfo.getQueryParameters(); - Map> queryParams = new MultivaluedHashMap<>(); - - if (queryParamsUnmodificable != null) - queryParams.putAll(queryParamsUnmodificable); - - - //List words = queryParams.get(searchParam); - /*if (words == null) { - words = new ArrayList<>(); - //words.add(EVENT_SESSION_FEEDBACK); - queryParams.put(SimpaticoResourceUtils.wordsParam, words); - } else { - words.add(EVENT_SESSION_FEEDBACK); - }*/ - - return SimpaticoResourceUtils.findRequest(request, queryParams, ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); - } catch (Exception e) { - SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); - return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); - } - - - } - - -} diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceTwitter.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceTwitter.java new file mode 100644 index 0000000..644aa15 --- /dev/null +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceTwitter.java @@ -0,0 +1,225 @@ +package es.hiiberia.simpatico.rest; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import es.hiiberia.simpatico.utils.ElasticSearchConnector; + +import org.apache.log4j.Logger; +import org.codehaus.jettison.json.JSONObject; +import org.elasticsearch.action.delete.DeleteResponse; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.sort.SortOrder; + +import es.hiiberia.simpatico.utils.SimpaticoProperties; +import es.hiiberia.simpatico.utils.Utils; + + +@Path("/twitter") +public class SimpaticoResourceTwitter { + + private static String ES_INDEX = "twitter"; + + private static String ES_TYPE = "TWT"; + private static String ES_FIELD_SEARCH = SimpaticoProperties.elasticSearchFieldSearch; + private static String FILE_LOG = SimpaticoProperties.simpaticoLog_Logs; + private static String THIS_RESOURCE = "TWT"; + + //atributos que meto porque los usa + private static int numLinesPrintStackInternalError = 1; + + //PREGUNTA SI PUEDES IMPRESION DE LAS COSAS MEDIANTE SYSTEM.OUT.PRINTLN() + + + + @GET + @Path("/prueba_get") + @Produces(MediaType.APPLICATION_JSON) + public Response find_formend(@Context HttpServletRequest request, @Context UriInfo uriInfo) { + + try { + // Copy map (it is unmodificable) + Map> queryParamsUnmodificable = uriInfo.getQueryParameters(); + Map> queryParams = new MultivaluedHashMap<>(); + + if (queryParamsUnmodificable != null){ + queryParams.putAll(queryParamsUnmodificable); + } + + + + Logger.getRootLogger().info("Los parametros son \n"+ queryParamsUnmodificable); + + ElasticSearchConnector inst= ElasticSearchConnector.getInstance(); + List busqueda= new ArrayList(); + + inst.createIndexWithDateField("borra2", "aux", "22-05-2010"); + + + return SimpaticoResourceUtils.findRequest(request, queryParams, "twitter", "tweet", "user", FILE_LOG, THIS_RESOURCE); + + + + /*for (Map.Entry> entry : queryParams.entrySet()) { + //String key = entry.getKey(); + List values = entry.getValue(); + for(String aux: values){ + busqueda.add(aux); + } + } + + Logger.getRootLogger().info("El array vale:"+ busqueda); + */ + + + /*SearchResponse responseES= inst.search("twitter", "msg", "user", busqueda, null, null, 4); + Logger.getRootLogger().info(responseES); + */ + //return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.searchResponse2JSONResponse(responseES)); + + //return SimpaticoResourceUtils.findRequest(request, queryParams, "twitter", "msg", "user", null, null); + + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + + } + + + + + + @POST + @Path("/prueba_post") + @Produces(MediaType.APPLICATION_JSON) + public Response prueba(@Context HttpServletRequest request, String postData){ + + try { + + + + ElasticSearchConnector connector= ElasticSearchConnector.getInstance(); + Logger.getRootLogger().info("Empiezo"); + + if(!connector.existsIndex("borra")){ + Logger.getRootLogger().info("Creando indice\n"); + connector.createIndex("borra"); + } + + // Check parameters and generate event attribute + JSONObject jsonObject = Utils.createJSONObjectIfValid(postData); + + Logger.getRootLogger().info("el json es el siguiente\n"+jsonObject.toString()); + + return SimpaticoResourceUtils.insertRequest(request, jsonObject.toString(), "twitter", "new", null, FILE_LOG, THIS_RESOURCE); + //return null; + + + //List index= new ArrayList(); + //index.add("fa"); + //index.add("dea"); + /*Logger.getRootLogger().info("Estoy aquiiiiiiiiiii3"); + Logger.getRootLogger().info(index); + if(inst.existsIndex("twitter")){ + Logger.getRootLogger().info("EXISTEEEEE"); + }*/ + + //inst.insertDocument("twitter", "aux", "2010-11-15T14:13:09"); + //DeleteResponse del= inst.deleteDocument ("twitter", "new", "AWBVNqwQFqYLB1TQZqTJ"); + //Logger.getRootLogger().info(del); + /*SearchResponse prueba= inst.search("shared", 1); + Logger.getRootLogger().info("primero"); + Logger.getRootLogger().info(prueba); + Logger.getRootLogger().info("segundo"); + SearchResponse aaa= inst.search("shared", "IFE", "averageTime", SortOrder.ASC, 4); + Logger.getRootLogger().info(aaa); + Logger.getRootLogger().info("tercero:"); + Logger.getRootLogger().info(postData); + + QueryBuilder qb= QueryBuilders.matchQuery("user", "James");*/ + + + } catch (Exception e) { + e.printStackTrace(); + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + + + + + + //inst.sear + //SearchResponse busqueda= inst.searchES("twitter", "msg", qb, null, null, 2); + + //Mirar esto para recibir body + //OJOOOOOOOO con el puerto de ARC y el de la ruta...utilizar 9300 para conectar a traves de aqui!!! + //uriInfo. + + + } + + + + //PUEDES BORRAR ESTA + @POST + @Path("/prueba_post_no_indice") + @Produces(MediaType.APPLICATION_JSON) + public Response prueba2(@Context HttpServletRequest request, String postData){ + + try { + + ElasticSearchConnector connector= ElasticSearchConnector.getInstance(); + Logger.getRootLogger().info("Empiezo"); + + + + + // Check parameters and generate event attribute + JSONObject jsonObject = Utils.createJSONObjectIfValid(postData); + + + return SimpaticoResourceUtils.insertRequest(request, jsonObject.toString(), "twitter", "new", null, FILE_LOG, THIS_RESOURCE); + + + + } catch (Exception e) { + e.printStackTrace(); + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + + } + + + + + @DELETE + @Path("/del/") + @Produces(MediaType.APPLICATION_JSON) + public Response testDelete(@Context HttpServletRequest request) { + Logger.getLogger(FILE_LOG).warn("[TEST] IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request)); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, "Welcome to SIMPATICO " + THIS_RESOURCE + " API! Method: DELETE"); + } + + +} + + diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceUtils.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceUtils.java index f44b30c..771260e 100644 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceUtils.java +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceUtils.java @@ -224,7 +224,9 @@ public static Response findRequest(HttpServletRequest request, UriInfo uriInfo, common_key= values.get(0); } else if(key.contentEquals("exist")){ - exist= values.get(0); + if (!values.isEmpty()) { + exist= values.get(0); + } // Search } else if(key.contentEquals("search")){ @@ -234,7 +236,6 @@ public static Response findRequest(HttpServletRequest request, UriInfo uriInfo, literalWords.add(splitWord); } } - literalWords.add("searchByField"); // Limit @@ -264,8 +265,8 @@ public static Response findRequest(HttpServletRequest request, UriInfo uriInfo, SearchResponse responseES; - if((common_key!=null)&&(!fieldSearch.equals(""))){ + //if(!fieldSearch.equals("")){ responseES = ElasticSearchConnector.getInstance().searchByKey_Value(ES_INDEX, ES_TYPE, common_key, fieldSearch, literalWords, fieldSortName, sortOrder, limit); // No params, so empty request -> return full documents stored @@ -275,7 +276,7 @@ public static Response findRequest(HttpServletRequest request, UriInfo uriInfo, //Calculate how many documents exist with this field. } else if (exist!=null) { - responseES = ElasticSearchConnector.getInstance().search_Exist(ES_INDEX, ES_TYPE, exist, fieldSortName, sortOrder, limit); + responseES = ElasticSearchConnector.getInstance().search_Exist(ES_INDEX, ES_TYPE, exist, common_key, fieldSortName, sortOrder, limit); } else if (literalWords.isEmpty()) { //Logger.getRootLogger().info("[RESOURCE_UTILS] NOP"); @@ -292,14 +293,11 @@ public static Response findRequest(HttpServletRequest request, UriInfo uriInfo, } //Filter the result when it's one of two. - if(fieldSearch.equals("slider_session_feedback_ctz")||fieldSearch.equals("slider_session_feedback_paragraph")){ - + if((fieldSearch.equals("slider_session_feedback_ctz")||fieldSearch.equals("slider_session_feedback_paragraph"))&&(!literalWords.isEmpty())){ Response query= SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.searchResponse2JSONResponse(responseES)); JSONArray JsonArray_aux= new JSONArray(); JSONObject jsonObject_final= new JSONObject(); JSONObject jsonObject_data; - - Logger.getRootLogger().info(query.getEntity().toString()); JSONObject jsonObject = Utils.createJSONObjectIfValid(query.getEntity().toString()); //Get an array with all JSONObject @@ -312,12 +310,11 @@ public static Response findRequest(HttpServletRequest request, UriInfo uriInfo, int valor= (int) jsonObject_data.get(fieldSearch); if(literalWords.contains(Integer.toString(valor))){ - JsonArray_aux.put((JSONObject) JSON_arr.get(i)); - } + JsonArray_aux.put((JSONObject) JSON_arr.get(i)); + } } - //Create a definitive JSONObject with values updates Create a document with updated values - + //Create a definitive JSONObject with values updates Iterator it= jsonObject.keys(); while(it.hasNext()){ String key= (String)it.next(); @@ -332,12 +329,10 @@ public static Response findRequest(HttpServletRequest request, UriInfo uriInfo, } } - return SimpaticoResourceUtils.createMessageResponse(jsonObject_final); } else { return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.searchResponse2JSONResponse(responseES)); - } } diff --git a/src/main/java/es/hiiberia/simpatico/utils/ElasticSearchConnector.java b/src/main/java/es/hiiberia/simpatico/utils/ElasticSearchConnector.java index ffa751b..fa7c3a9 100644 --- a/src/main/java/es/hiiberia/simpatico/utils/ElasticSearchConnector.java +++ b/src/main/java/es/hiiberia/simpatico/utils/ElasticSearchConnector.java @@ -207,30 +207,35 @@ public SearchResponse searchByField(String index, String type, String field, Lis } /** - *Method that searches for all the words that have de common key + *Method that searches for all the words that have the common key * @param common_key - * @param words: array with all values to search + * @param words: Optional. Array with all values to search * @param fieldSort: _all * @throws IOException */ - public SearchResponse searchByAnyField(String index, String type, String field, String common_key, List words, String fieldSort, SortOrder ord, int limit) throws IOException { BoolQueryBuilder boolQuery = new BoolQueryBuilder(); BoolQueryBuilder boolQuery2 = new BoolQueryBuilder(); for (String word : words) { boolQuery2.should(QueryBuilders.matchQuery(field, word)); } - boolQuery.must(QueryBuilders.matchQuery(field, common_key)).must(boolQuery2); - return searchES (index, type, boolQuery, fieldSort, ord, limit); + //query with common_key + if(common_key!= null){ + boolQuery.must(QueryBuilders.matchQuery(field, common_key)).must(boolQuery2); + return searchES (index, type, boolQuery, fieldSort, ord, limit); + + //query without common_key + } else{ + return searchES (index, type, boolQuery2, fieldSort, ord, limit); + } } - /** - * Search all documents with match between field: word and also have common_key. + * Search all documents with match between field:word with common_key. * @param common_key * @param field: name of field - * @param words: have to match with field + * @param words: Optional. * @throws IOException */ public SearchResponse searchByKey_Value(String index, String type, String common_key, String field, List words, String fieldSort, SortOrder ord, int limit) throws IOException { @@ -238,32 +243,41 @@ public SearchResponse searchByKey_Value(String index, String type, String common String field_common_key= "_all"; BoolQueryBuilder boolQuery = new BoolQueryBuilder(); BoolQueryBuilder boolQuery2 = new BoolQueryBuilder(); - for (String word : words) { - boolQuery2.should(QueryBuilders.matchQuery(field, word)); - } - - boolQuery.must(QueryBuilders.matchQuery(field_common_key, common_key)).must(boolQuery2); - - return searchES (index, type, boolQuery, fieldSort, ord, limit); + //query with words + if (!words.isEmpty()){ + for (String word : words) { + boolQuery2.should(QueryBuilders.matchQuery(field, word)); + } + boolQuery.must(QueryBuilders.matchQuery(field_common_key, common_key)).must(boolQuery2); + //query without words + } else{ + boolQuery.must(QueryBuilders.matchQuery(field_common_key, common_key)); + } + return searchES (index, type, boolQuery, fieldSort, ord, limit); } -/** - * Calculates number of document with name of field= exist. - * @param exist: name of field to search. - * @throws IOException - */ - public SearchResponse search_Exist(String index, String type, String exist, String fieldSort, SortOrder ord, int limit) throws IOException { + /** + * Calculates the number of documents with field name equal to exist. + * @param exist: name of field to search. + * @param common_key + * @throws IOException + */ + public SearchResponse search_Exist(String index, String type, String exist, String common_key, String fieldSort, SortOrder ord, int limit) throws IOException { BoolQueryBuilder boolQuery = new BoolQueryBuilder(); - boolQuery.must(QueryBuilders.existsQuery(exist)); - return searchES (index, type, boolQuery, fieldSort, ord, limit); - + BoolQueryBuilder boolQuery2 = new BoolQueryBuilder(); + boolQuery.must(QueryBuilders.existsQuery(exist)); + if (common_key!=null){ + boolQuery2.must(QueryBuilders.matchQuery("_all", common_key)).must(boolQuery); + return searchES (index, type, boolQuery2, fieldSort, ord, limit); + } else { + return searchES (index, type, boolQuery, fieldSort, ord, limit); + + } } - - private SearchResponse searchES (String index, String type, QueryBuilder qb, String fieldSort, SortOrder ord, int limit) throws IOException { try { From 0644a25e9991757666efcb1c34fc1befe9307e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20M=C3=A9ndez?= Date: Thu, 1 Mar 2018 12:44:12 +0100 Subject: [PATCH 08/23] Create .travis.yml --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6bba276 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: java +jdk: + - oraclejdk8 From a1dd7dd34ab96f866d6cb3a8dcface925ceeea76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20M=C3=A9ndez?= Date: Thu, 1 Mar 2018 12:45:55 +0100 Subject: [PATCH 09/23] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c1597eb..cf4d46f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.org/SIMPATICOProject/logs.svg?branch=master)](https://travis-ci.org/SIMPATICOProject/logs) + # Logs Logs is a module for store/search/update multiple data from another modules. From 9ef881ab360d0c14d5d117dd673bc12cfcf19bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20M=C3=A9ndez?= Date: Thu, 1 Mar 2018 13:30:56 +0100 Subject: [PATCH 10/23] Update .travis.yml --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6bba276..4bbc00e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,8 @@ language: java jdk: - oraclejdk8 + +script: "mvn cobertura:cobertura" + +after_success: + - bash <(curl -s https://codecov.io/bash) From c7037160629365d88d33e315f86dd3d45b904d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20M=C3=A9ndez?= Date: Thu, 1 Mar 2018 13:33:33 +0100 Subject: [PATCH 11/23] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cf4d46f..bc8fa7a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![Build Status](https://travis-ci.org/SIMPATICOProject/logs.svg?branch=master)](https://travis-ci.org/SIMPATICOProject/logs) +[![codecov](https://codecov.io/gh/SIMPATICOProject/logs/branch/master/graph/badge.svg)](https://codecov.io/gh/SIMPATICOProject/logs) # Logs Logs is a module for store/search/update multiple data from another modules. From 5f8405db9056a179815cbdb90b61fc3307689227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20M=C3=A9ndez?= Date: Thu, 1 Mar 2018 14:04:58 +0100 Subject: [PATCH 12/23] [ADD] Codecov plugin to pom.xml --- pom.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pom.xml b/pom.xml index 0b3f547..c611564 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,18 @@ /${project.build.finalName} + + org.codehaus.mojo + cobertura-maven-plugin + 2.7 + + + html + xml + + + + From 2c9599545d757539c8a460023b81ee428863e7ba Mon Sep 17 00:00:00 2001 From: paubert Date: Fri, 13 Apr 2018 14:44:31 +0200 Subject: [PATCH 13/23] [ADD] Docker stuff --- Dockerfile | 15 +++++++++++++ docker-compose.yml | 10 +++++++++ docker-configs/simpatico.properties | 34 +++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 docker-configs/simpatico.properties diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1c5e65e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM maven:3.5.3-jdk-8 as builder + +WORKDIR /app + +COPY . /app +COPY docker-configs/simpatico.properties src/main/resources/simpatico.properties + +RUN mvn clean package + + +FROM tomcat:7 + +COPY --from=builder /app/target/simpatico.war /usr/local/tomcat/webapps/simpatico.war + +EXPOSE 8080 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1c2690a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3' + +services: + webapp: + build: + context: . + ports: + - 8899:8080 + volumes: + - .:/usr/src/mymaven diff --git a/docker-configs/simpatico.properties b/docker-configs/simpatico.properties new file mode 100644 index 0000000..1d06695 --- /dev/null +++ b/docker-configs/simpatico.properties @@ -0,0 +1,34 @@ +# Simpatico + +# Elastic search config +elasticsearch.ip=192.168.26.71 +elasticsearch.port=9300 +elasticsearch.clustername=simpatico + +elasticsearch.hi.index=hi +elasticsearch.shared.index=shared +elasticsearch.piwik.index=piwik +elasticsearch.piwik.type=piwikAnalytics + +# Piwik +piwik.api_url=http://127.0.0.1:90/piwik/?module=API +piwik.auth_token=635b8c4939d08a031ee40acbc8626921 + +# Name created field (timestamp) +elasticsearch.created.field.name=created +# Field name to search (_all search in all indices) +elasticsearch.search.field=_all + +# Authentication (AAC) +authentication.use=true +authentication.url=https://simpatico.hi-iberia.es:4570/aac +# AAC Get auth. Basic auth. Header Authorization: Basic +authentication.getauth.user=simpatico +authentication.getauth.pass=#s1mp4t1c0# +# Allow IPs requests (For other components) +authentication.whitelist.ip=localhost,127.0.0.1 +authentication.domains.allowed=simpatico.hi-iberia.es:4570/simpatico/api/logs/find,simpatico.hi-iberia.es:4570/simpatico/api/ife/find,simpatico.hi-iberia.es:4570/simpatico/api/logs/reduction-time-spent,simpatico.hi-iberia.es:4570/simpatico/api/logs/percentage-complete-autonomously +authentication.referers.domains.allowed=simpatico-dashboard,simpatico.hi-iberia.es:4570/IFE/ + +# Real Ip header name (from nginx for example) +http.header.realip=x-real-ip From b3814390ddffd56ff7c10b0f4e9770a0c10fe80f Mon Sep 17 00:00:00 2001 From: mirkoperillo Date: Fri, 25 May 2018 16:42:43 +0200 Subject: [PATCH 14/23] added stuff to create docker image for the platform --- Dockerfile.platform | 23 +++++++++++++ docker-configs/simpatico.platform.properties | 34 ++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 Dockerfile.platform create mode 100644 docker-configs/simpatico.platform.properties diff --git a/Dockerfile.platform b/Dockerfile.platform new file mode 100644 index 0000000..b7c4f8c --- /dev/null +++ b/Dockerfile.platform @@ -0,0 +1,23 @@ +FROM maven:3.5.2-jdk-8-alpine + +WORKDIR /app + +COPY . /app + +COPY docker-configs/simpatico.platform.properties src/main/resources/simpatico.properties + +RUN apk add --update openssl + +# install tomcat +WORKDIR /servers +RUN wget -O apache-tomcat.tgz https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.28/bin/apache-tomcat-8.5.28.tar.gz +RUN tar xzvf apache-tomcat.tgz +RUN rm apache-tomcat.tgz + +WORKDIR /app +RUN mvn clean package -Dmaven.test.skip=true && cp target/simpatico.war /servers/apache-tomcat-8.5.28/webapps + +WORKDIR /servers +CMD apache-tomcat-8.5.28/bin/catalina.sh run + +EXPOSE 8080 diff --git a/docker-configs/simpatico.platform.properties b/docker-configs/simpatico.platform.properties new file mode 100644 index 0000000..56cd7b7 --- /dev/null +++ b/docker-configs/simpatico.platform.properties @@ -0,0 +1,34 @@ +# Simpatico + +# Elastic search config +elasticsearch.ip=es +elasticsearch.port=9300 +elasticsearch.clustername=simpatico + +elasticsearch.hi.index=hi +elasticsearch.shared.index=shared +elasticsearch.piwik.index=piwik +elasticsearch.piwik.type=piwikAnalytics + +# Piwik +piwik.api_url=${PIWIK_API_URL} +piwik.auth_token=${PIWIK_URL} + +# Name created field (timestamp) +elasticsearch.created.field.name=created +# Field name to search (_all search in all indices) +elasticsearch.search.field=_all + +# Authentication (AAC) +authentication.use=${AUTH_ENABLE} +authentication.url=${AUTH_URL} +# AAC Get auth. Basic auth. Header Authorization: Basic +authentication.getauth.user=${AUTH_USER} +authentication.getauth.pass=${AUTH_PWD} +# Allow IPs requests (For other components) +authentication.whitelist.ip=${AUTH_WHITELIST} +authentication.domains.allowed=${AUTH_DOMAINS} +authentication.referers.domains.allowed=${AUTH_REF_DOMAINS} + +# Real Ip header name (from nginx for example) +http.header.realip=x-real-ip From 8c1c209e34ced8f9f651093f042803a25719bb2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20M=C3=A9ndez?= Date: Wed, 30 May 2018 09:58:40 +0200 Subject: [PATCH 15/23] [ADD] .pdf file with documentation about SF. Updated README.md --- README.md | 5 +++++ SF_Configuration.pdf | Bin 0 -> 28567 bytes 2 files changed, 5 insertions(+) create mode 100644 SF_Configuration.pdf diff --git a/README.md b/README.md index bc8fa7a..5cf7bd8 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,11 @@ Logs is a module for store/search/update multiple data from another modules. ## Configuration Swagger files  1. Open each file in `src/main/webapp/dist/yaml_files` and change `host` and `basePath, schemes` if you want + +## Configuration Session Feedback Questions + In the root of this project you can find a `SF_Configuration.pdf` file explaining how to create the JSON file use to create the questions for the Session Feedback modal at the end of each service. We are in the process of updating the Swagger file with the new endpoints and its structure, but in the meantime there is the `SF_Configuration.pdf` file. + + The goal is to create one JSON for each service in each language. ## Configuration Java project diff --git a/SF_Configuration.pdf b/SF_Configuration.pdf new file mode 100644 index 0000000000000000000000000000000000000000..321ce527e02dcbc32ec5ae34e9061eda06a3eee4 GIT binary patch literal 28567 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 z Date: Thu, 31 May 2018 10:26:34 +0200 Subject: [PATCH 16/23] [IMP] Session Feedback now created via configuration, no html here. Created CRUD endpoints for the configuration. --- .../simpatico/rest/SimpaticoResourceSF.java | 541 ++++++++++++------ .../utils/ElasticSearchConnector.java | 16 +- .../es/hiiberia/simpatico/utils/Forms.java | 241 -------- .../simpatico/utils/SimpaticoProperties.java | 6 + src/main/resources/simpatico.properties | 1 + src/main/webapp/WEB-INF/web.xml | 4 + 6 files changed, 371 insertions(+), 438 deletions(-) delete mode 100644 src/main/java/es/hiiberia/simpatico/utils/Forms.java diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java index d7fb69d..a826fa9 100644 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java @@ -22,12 +22,12 @@ import org.apache.log4j.Logger; import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.search.sort.SortOrder; import es.hiiberia.simpatico.utils.ElasticSearchConnector; -import es.hiiberia.simpatico.utils.Forms; import es.hiiberia.simpatico.utils.SimpaticoProperties; import es.hiiberia.simpatico.utils.Utils; @@ -43,7 +43,7 @@ public class SimpaticoResourceSF { private static String USER_ID = "userID"; private static String E_SERVICE_ID = "e-serviceID"; - private static String COMPLEXITY = "complexity"; +// private static String COMPLEXITY = "complexity"; private static String EVENT = "event"; private static String EVENT_SESSION_FEEDBACK = "session_feedback"; @@ -55,28 +55,28 @@ public class SimpaticoResourceSF { @Produces(MediaType.APPLICATION_JSON) public Response find_sf(@Context HttpServletRequest request, @Context UriInfo uriInfo) { - try { - // Copy map (it is unmodificable) - Map> queryParamsUnmodificable = uriInfo.getQueryParameters(); - Map> queryParams = new MultivaluedHashMap<>(); - - if (queryParamsUnmodificable != null) - queryParams.putAll(queryParamsUnmodificable); - - List words = queryParams.get(SimpaticoResourceUtils.wordsParam); - if (words == null) { - words = new ArrayList<>(); - words.add(EVENT_SESSION_FEEDBACK); - queryParams.put(SimpaticoResourceUtils.wordsParam, words); - } else { - words.add(EVENT_SESSION_FEEDBACK); - } - - return SimpaticoResourceUtils.findRequest(request, queryParams, ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); - } catch (Exception e) { - SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); - return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); - } + try { + // Copy map (it is unmodificable) + Map> queryParamsUnmodificable = uriInfo.getQueryParameters(); + Map> queryParams = new MultivaluedHashMap<>(); + + if (queryParamsUnmodificable != null) + queryParams.putAll(queryParamsUnmodificable); + + List words = queryParams.get(SimpaticoResourceUtils.wordsParam); + if (words == null) { + words = new ArrayList<>(); + words.add(EVENT_SESSION_FEEDBACK); + queryParams.put(SimpaticoResourceUtils.wordsParam, words); + } else { + words.add(EVENT_SESSION_FEEDBACK); + } + + return SimpaticoResourceUtils.findRequest(request, queryParams, ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } } /** @@ -89,32 +89,29 @@ public Response find_sf(@Context HttpServletRequest request, @Context UriInfo ur @Path("/insert") @Produces(MediaType.APPLICATION_JSON) public Response insert(@Context HttpServletRequest request, String postData) { - /* JSON: {userID: , e-serviceID: , complexity: } event: session_feedback - */ - - boolean badRequest = true; - - try { - // Check parameters and generate event attribute - JSONObject jsonObject = Utils.createJSONObjectIfValid(postData); - if (jsonObject != null) { - if (jsonObject.has(USER_ID) && jsonObject.has(E_SERVICE_ID) && jsonObject.has(COMPLEXITY)) { - badRequest = false; - jsonObject.put(EVENT, EVENT_SESSION_FEEDBACK); - } - } + boolean badRequest = true; - if (!badRequest) { - return SimpaticoResourceUtils.insertRequest(request, jsonObject.toString(), ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); + try { + // Check parameters and generate event attribute + JSONObject jsonObject = Utils.createJSONObjectIfValid(postData); + if (jsonObject != null) { + if (jsonObject.has(USER_ID) && jsonObject.has(E_SERVICE_ID)) { + badRequest = false; + jsonObject.put(EVENT, EVENT_SESSION_FEEDBACK); + } + } + + if (!badRequest) { + return SimpaticoResourceUtils.insertRequest(request, jsonObject.toString(), ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); + } + + Logger.getLogger(FILE_LOG).warn("[BAD REQUEST] Insert document. IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request) + ". POST data: " + postData); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, SimpaticoResourceUtils.badPOSTRequestResponse); + + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); } - - Logger.getLogger(FILE_LOG).warn("[BAD REQUEST] Insert document. IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request) + ". POST data: " + postData); - return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, SimpaticoResourceUtils.badPOSTRequestResponse); - - } catch (Exception e) { - SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); - return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); - } } /** @@ -129,33 +126,33 @@ public Response insert(@Context HttpServletRequest request, String postData) { @Path("/update") @Produces(MediaType.APPLICATION_JSON) public Response update(@Context HttpServletRequest request, String postData) { - // Check params like insert - boolean badRequest = true; - - try { - // Check parameters and generate event attribute - JSONObject jsonObject = Utils.createJSONObjectIfValid(postData); - if (jsonObject != null) { - if (jsonObject != null) { - if (jsonObject.has(USER_ID) && jsonObject.has(E_SERVICE_ID) && jsonObject.has(COMPLEXITY)) { - badRequest = false; - jsonObject.put(EVENT, EVENT_SESSION_FEEDBACK); - } - } - } + // Check params like insert + boolean badRequest = true; - if (!badRequest) { - - return SimpaticoResourceUtils.updateRequest(request, jsonObject.toString(), ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); + try { + // Check parameters and generate event attribute + JSONObject jsonObject = Utils.createJSONObjectIfValid(postData); + if (jsonObject != null) { + if (jsonObject != null) { + if (jsonObject.has(USER_ID) && jsonObject.has(E_SERVICE_ID)) { + badRequest = false; + jsonObject.put(EVENT, EVENT_SESSION_FEEDBACK); + } + } + } + + if (!badRequest) { + + return SimpaticoResourceUtils.updateRequest(request, jsonObject.toString(), ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); + } + + Logger.getLogger(FILE_LOG).warn("[BAD REQUEST] Insert document. IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request) + ". POST data: " + postData); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, SimpaticoResourceUtils.badPOSTRequestResponse); + + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); } - - Logger.getLogger(FILE_LOG).warn("[BAD REQUEST] Insert document. IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request) + ". POST data: " + postData); - return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, SimpaticoResourceUtils.badPOSTRequestResponse); - - } catch (Exception e) { - SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); - return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); - } } /** @@ -168,126 +165,280 @@ public Response update(@Context HttpServletRequest request, String postData) { @Path("/remove") @Produces(MediaType.APPLICATION_JSON) public Response remove(@Context HttpServletRequest request, String postData) { - try { - return SimpaticoResourceUtils.removeRequest(request, postData, ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); - } catch (Exception e) { - SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); - return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); - } + try { + return SimpaticoResourceUtils.removeRequest(request, postData, ES_INDEX, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } } @GET @Path("/selectdialog") - @Produces(MediaType.TEXT_HTML) - public String selectDialog(@QueryParam("id") String userId, @QueryParam("ctz") Boolean ctz, @QueryParam("simpl") Boolean simpl, @QueryParam("timeout") Boolean timeout, - @QueryParam("lang") String lang) { - // Initialize the forms with the correct language - Forms form = new Forms(lang); - boolean wordSimp = false; - boolean phraseSimp = false; - boolean paragraphSimp = false; - boolean wae = false; - - // Search in ES what the user has done since he started the last session - try { - // XXX: Y si primero hacer una quuery para coger el último "session_start" y luego ya esta? - // No la hago todavía por el lío más abajo explicado del "must" y el "should" - List words = new ArrayList(); - words.add("paragraph_simplification"); - words.add("word_simplification"); - words.add("free_text_simplification"); - words.add("citizenpedia_content_request"); - words.add("session_start"); - // XXX: Al hacer la query, pone un "should" en vez de un "must". Me salen resultados con "form_start" también. Pero con "must" no me salen resultados - SearchResponse responseES = ElasticSearchConnector.getInstance().searchByField("shared", null, "event", words, "created", SortOrder.DESC, 30, userId); - JSONObject jsonRes = SimpaticoResourceUtils.searchResponse2JSONResponse(responseES); - - // Search in the results the info needed - // XXX: Dos bucles. Uno solo para el último "session_start" y otro para el resto de cosas. Se soluciona cuando se solucione lo de arriba - JSONArray results = jsonRes.getJSONArray("results"); - String lastSession = ""; - for (int i=0; i words = new ArrayList(); + words.add("paragraph_simplification"); + words.add("word_simplification"); + words.add("free_text_simplification"); + words.add("citizenpedia_content_request"); + words.add("workflow_adaptation_request"); + words.add("session_start"); + // XXX: Al hacer la query, pone un "should" en vez de un "must". Me salen resultados con "form_start" también. Pero con "must" no me salen resultados + int limit = 30; + SearchResponse responseES = ElasticSearchConnector.getInstance().searchByField("shared", null, "event", words, "created", SortOrder.DESC, limit, userId); + JSONObject jsonRes = SimpaticoResourceUtils.searchResponse2JSONResponse(responseES); + + // Search in the results the info needed + // XXX: Dos bucles. Uno solo para el último "session_start" y otro para el resto de cosas. Se soluciona cuando se solucione lo de arriba + JSONArray results = jsonRes.getJSONArray("results"); + Logger.getRootLogger().info(results.length()); + String lastSession = searchLastSession(results); + while (lastSession.isEmpty()) { + // Keep searching until finding the last "session_start" event + limit = limit*5; + responseES = ElasticSearchConnector.getInstance().searchByField("shared", null, "event", words, "created", SortOrder.DESC, limit, userId); + jsonRes = SimpaticoResourceUtils.searchResponse2JSONResponse(responseES); + results = jsonRes.getJSONArray("results"); + Logger.getRootLogger().info(results.length()); + lastSession = searchLastSession(results); + } + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + Date lastSessionDate = sdf.parse(lastSession); + for (int i=0; i(); + words.add(eserviceId); + responseES = ElasticSearchConnector.getInstance().search(SimpaticoProperties.elasticSearchSFQuestionsIndex, ES_TYPE, "common.eserviceId", words, null, null, 20); + jsonRes = SimpaticoResourceUtils.searchResponse2JSONResponse(responseES); + results = jsonRes.getJSONArray("results"); - if (event.equals("wae")) { -// Logger.getRootLogger().info("WAE event in ES search"); - if (date.after(lastSessionDate)) { -// Logger.getRootLogger().info("WAE event AFTER last session"); - wae = true; + JSONObject serviceConfig = null; + if (results.length() > 0) { + // Get the config JSON for the correspondent lang (not possible to do it in the search) + for (int i=0; i words = new ArrayList<>(); + words.add(eservice); + SearchResponse responseES = ElasticSearchConnector.getInstance().search(SimpaticoProperties.elasticSearchSFQuestionsIndex, ES_TYPE, "common.eserviceId", words, null, null, 20); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.searchResponse2JSONResponse(responseES)); + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + } + } + + @POST + @Path("/config") + @Produces(MediaType.APPLICATION_JSON) + public Response addNewConfig(@Context HttpServletRequest request, String postData) { + boolean badRequest = true; + + try { + // Check parameters and generate event attribute + JSONObject jsonObject = Utils.createJSONObjectIfValid(postData); + Logger.getRootLogger().info(jsonObject.toString(2)); + if (jsonObject != null) { + if (jsonObject.getJSONObject("common").has("lang") && jsonObject.getJSONObject("common").has("eserviceId")) { + badRequest = false; + } + } + + if (!badRequest) { + return SimpaticoResourceUtils.insertRequest(request, jsonObject.toString(), SimpaticoProperties.elasticSearchSFQuestionsIndex, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); + } + + Logger.getLogger(FILE_LOG).warn("[BAD REQUEST] Insert document. IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request) + ". POST data: " + postData); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "Parameters eserviceId and lang are mandatory"); + + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + } + + /** + * Update a json document. The postData must be a valid json and format: {"id": "", "content": ""}. + * If json has the same keys that the old document, the document updates. + * If the document does not exists, it is created. + * @param request + * @param postData + * @return + */ + @PUT + @Path("/config") + @Produces(MediaType.APPLICATION_JSON) + public Response updateConfig(@Context HttpServletRequest request, String postData) { + try { + return SimpaticoResourceUtils.updateRequest(request, postData, SimpaticoProperties.elasticSearchSFQuestionsIndex, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + } + + /** + * Remove a json document. The postData must be a valid json and format: {"id": ""} + * @param request + * @param postData + * @return + */ + @DELETE + @Path("/config") + @Produces(MediaType.APPLICATION_JSON) + public Response removeConfig(@Context HttpServletRequest request, String postData) { + try { + return SimpaticoResourceUtils.removeRequest(request, postData, SimpaticoProperties.elasticSearchSFQuestionsIndex, ES_TYPE, ES_FIELD_SEARCH, FILE_LOG, THIS_RESOURCE); + } catch (Exception e) { + SimpaticoResourceUtils.logException(e, FILE_LOG, THIS_RESOURCE); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } } /** Test Method **/ @@ -295,31 +446,45 @@ public String selectDialog(@QueryParam("id") String userId, @QueryParam("ctz") B @Path("/test/") @Produces(MediaType.APPLICATION_JSON) public Response testGet(@Context HttpServletRequest request) { - Logger.getLogger(FILE_LOG).warn("[TEST] IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request)); - return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, "Welcome to SIMPATICO " + THIS_RESOURCE + " API! Method: GET"); + Logger.getLogger(FILE_LOG).warn("[TEST] IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request)); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, "Welcome to SIMPATICO " + THIS_RESOURCE + " API! Method: GET"); } @POST @Path("/test/") @Produces(MediaType.APPLICATION_JSON) public Response testPost(@Context HttpServletRequest request) { - Logger.getLogger(FILE_LOG).warn("[TEST] IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request)); - return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, "Welcome to SIMPATICO " + THIS_RESOURCE + " API! Method: POST"); + Logger.getLogger(FILE_LOG).warn("[TEST] IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request)); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, "Welcome to SIMPATICO " + THIS_RESOURCE + " API! Method: POST"); } @PUT @Path("/test/") @Produces(MediaType.APPLICATION_JSON) public Response testPut(@Context HttpServletRequest request) { - Logger.getLogger(FILE_LOG).warn("[TEST] IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request)); - return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, "Welcome to SIMPATICO " + THIS_RESOURCE + " API! Method: PUT"); + Logger.getLogger(FILE_LOG).warn("[TEST] IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request)); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, "Welcome to SIMPATICO " + THIS_RESOURCE + " API! Method: PUT"); } @DELETE @Path("/test/") @Produces(MediaType.APPLICATION_JSON) public Response testDelete(@Context HttpServletRequest request) { - Logger.getLogger(FILE_LOG).warn("[TEST] IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request)); + Logger.getLogger(FILE_LOG).warn("[TEST] IP Remote: " + request.getRemoteAddr() + ". IP Header Real: " + SimpaticoResourceUtils.getRealIPHeader(request)); return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverOkCode, "Welcome to SIMPATICO " + THIS_RESOURCE + " API! Method: DELETE"); } + + private String searchLastSession(JSONArray results) throws JSONException { + String lastSession = ""; + for (int i=0; i words, String fieldSort, SortOrder ord, int limit) throws IOException { + public SearchResponse searchByKey_Value(String index, String type, String common_value, String field, List words, String fieldSort, SortOrder ord, int limit) throws IOException { //Normally common_key is e-service String field_common_key= "_all"; BoolQueryBuilder boolQuery = new BoolQueryBuilder(); @@ -248,11 +246,11 @@ public SearchResponse searchByKey_Value(String index, String type, String common for (String word : words) { boolQuery2.should(QueryBuilders.matchQuery(field, word)); } - boolQuery.must(QueryBuilders.matchQuery(field_common_key, common_key)).must(boolQuery2); + boolQuery.must(QueryBuilders.matchQuery(field_common_key, common_value)).must(boolQuery2); //query without words - } else{ - boolQuery.must(QueryBuilders.matchQuery(field_common_key, common_key)); + } else { + boolQuery.must(QueryBuilders.matchQuery(field_common_key, common_value)); } return searchES (index, type, boolQuery, fieldSort, ord, limit); diff --git a/src/main/java/es/hiiberia/simpatico/utils/Forms.java b/src/main/java/es/hiiberia/simpatico/utils/Forms.java deleted file mode 100644 index 82d5bb5..0000000 --- a/src/main/java/es/hiiberia/simpatico/utils/Forms.java +++ /dev/null @@ -1,241 +0,0 @@ -package es.hiiberia.simpatico.utils; - -/** - * - * This class get the language from the website and constructs the SF dialog to show to the user. - * - */ -public class Forms { - // i18n - private String button_cancel; - private String button_send; - private String slider_useful; - private String slider_unuseful; - - private String common_faces; - private String common_comments; - private String common_opinion_placeholder; - - private String simpl_paragraph; - private String simpl_phrase; - private String simpl_word; - - private String ctz_slider; - - private String timeout; - private String timeout_placeholder; - - private String wae_steps; - private String wae_blocks; - - public Forms(String lang) { - if (lang.equals("es")) { - /** Spanish **/ - button_cancel = "No quiero"; - button_send = "Enviar"; - slider_useful = "Muy útil"; - slider_unuseful = "Nada útil"; - - common_faces = "¿Cree que en conjunto las herramientas de SIMPATICO le facilitaron la comprensión del servicio?"; - common_comments = "¿Tiene alguna sugerencia para los desarrolladores de SIMPATICO?"; - common_opinion_placeholder = "Escriba su opinión..."; - - simpl_paragraph = "¿Fue la simplificación de párrafo que pidió útil para comprender mejor el servicio?"; - simpl_phrase = "¿Fue la simplificación de frase que pidió útil para comprender mejor el servicio?"; - simpl_word = "¿Fue la simplificación de palabra que pidió útil para comprender mejor el servicio?"; - - ctz_slider = "¿Fue útil su consulta a la Citizenpedia?"; - - timeout = "La sesión tardó en completarse más de lo acostumbrado. ¿Tuvo algún problema con algún elemento del servicio?"; - timeout_placeholder = "Explique los problemas encontrados..."; - - wae_steps = "Fue útil la guía paso a paso?"; - wae_blocks = "Fue útil la organizacin en bloques del servicio?"; - } else if (lang.equals("it")) { - /** Italian **/ - button_cancel = "Annulla"; - button_send = "Inviare"; - slider_useful = "Molto utile"; - slider_unuseful = "Non è utile"; - - common_faces = "Pensi che gli strumenti di SIMPATICO falicilitino la comprensione del servizio e la compilazione del modulo?"; - common_comments = "Hai qualche suggerimento per gli sviluppatori di SIMPATICO?"; - common_opinion_placeholder = "Scrivi una recensione..."; - - simpl_paragraph = "La semplificazione del paragrafo ti ha aiutato a capire meglio il senso di quanto richiesto?"; - simpl_phrase = "La semplificazione della frase ti ha aiutato a capire meglio il senso di quanto richiesto?"; - simpl_word = "La semplificazione della singola parola ti ha aiutato a capire meglio il senso di quanto richiesto?"; - - ctz_slider = "Quanto sono state utili le informazioni disponibili in Citizenpedia?"; - - timeout = "La tua sessione ha richiesto piu' tempo del previsto. Hai incontrato qualche difficoltà nella compilazione del modulo?"; - timeout_placeholder = "Spiegare i problemi incontrati..."; - - wae_steps = "Quanto e' stata utile la guida passo passo?"; - wae_blocks = "Quanto e' stata utile l'organizzazione in blocchi del modulo?"; - } else { - /** English (default) **/ - button_cancel = "Cancel"; - button_send = "Send"; - slider_useful = "Very useful"; - slider_unuseful = "Not useful"; - - common_faces = "Do you think that the SIMPATICO tools together facilitated the understanding of the service?"; - common_comments = "Do you have any suggestions for SIMPATICO developers?"; - common_opinion_placeholder = "Write your review..."; - - simpl_paragraph = "Was the paragraph simplification you asked for useful to better understand the service?"; - simpl_phrase = "Was the simplification of sentence you asked for useful to better understand the service?"; - simpl_word = "Was the simplification of word you asked for useful to better understand the service?"; - - ctz_slider = "How useful was your inquiry to the Citizenpedia?"; - - timeout = "The session took longer than usual. Did you have a problem with any element of the service?"; - timeout_placeholder = "Explain the problems encountered..."; - - wae_steps = "How much helpful has been the step-by-step guide?"; - wae_blocks = "How much helpful has been the organization in blocks of the service?"; - } - } - - - // Pieces of different form's divs - public String getStartingDiv() { - return "
"; - } - - public String getCommonPart() { - return ""+ - "
"+ - "
"+ - common_faces + - "
"+ - "
"+ - ""+ - ""+ - ""+ - ""+ - ""+ - ""+ - "
"+ - "
"+ - ""+ - "
"+ - "
"+ - common_comments + - "
"+ - "
"+ - ""+ - "
"+ - "
"+ - ""+ - "
"+ - ""+ - "
"+ - ""+ button_send +""+ - "
"+ - "
"+ - "
"; - } - - public String getParagraphSimplificationPart() { - return ""+ - "
"+ - "
"+ - simpl_paragraph + - "
"+ - "
"+ - ""+ - "
"+ slider_unuseful +"
"+ - "
"+ slider_useful +"
"+ - "
0
"+ - "
"+ - "
"; - } - - public String getPhraseSimplificationPart() { - return ""+ - "
"+ - "
"+ - simpl_phrase + - "
"+ - "
"+ - ""+ - "
"+ slider_unuseful +"
"+ - "
"+ slider_useful +"
"+ - "
0
"+ - "
"+ - "
"; - } - - public String getWordSimplificationPart() { - return ""+ - "
"+ - "
"+ - simpl_word + - "
"+ - "
"+ - ""+ - "
"+ slider_unuseful +"
"+ - "
"+ slider_useful +"
"+ - "
0
"+ - "
"+ - "
"; - } - - public String getCtzPart() { - return ""+ - "
"+ - "
"+ - ctz_slider + - "
"+ - "
"+ - ""+ - "
"+ slider_unuseful +"
"+ - "
"+ slider_useful +"
"+ - "
0
"+ - "
"+ - "
"; - } - - public String getTimeoutPart() { - return ""+ - "
"+ - "
"+ - timeout + - "
"+ - "
"+ - ""+ - "
"+ - "
"; - } - - public String getWAEPart() { - return ""+ - "
"+ - "
"+ - wae_steps + - "
"+ - "
"+ - ""+ - "
"+ slider_unuseful +"
"+ - "
"+ slider_useful +"
"+ - "
0
"+ - "
"+ - "
"+ - ""+ - "
"+ - "
"+ - wae_blocks + - "
"+ - "
"+ - ""+ - "
"+ slider_unuseful +"
"+ - "
"+ slider_useful +"
"+ - "
0
"+ - "
"+ - "
"; - } -} diff --git a/src/main/java/es/hiiberia/simpatico/utils/SimpaticoProperties.java b/src/main/java/es/hiiberia/simpatico/utils/SimpaticoProperties.java index 120f607..a9135be 100644 --- a/src/main/java/es/hiiberia/simpatico/utils/SimpaticoProperties.java +++ b/src/main/java/es/hiiberia/simpatico/utils/SimpaticoProperties.java @@ -33,6 +33,9 @@ public class SimpaticoProperties { public static String elasticSearchPiwikIndex; public static String elasticSearchPiwikType; + // Session feedback configuration JSONs + public static String elasticSearchSFQuestionsIndex; + // Authentication AAC public static Boolean aacUse; public static String aacUrlServer; @@ -85,6 +88,9 @@ public static boolean getStrings() { elasticSearchPiwikIndex = RESOURCE_BUNDLE.getString("elasticsearch.piwik.index"); elasticSearchPiwikType = RESOURCE_BUNDLE.getString("elasticsearch.piwik.type"); + // Session feedback configuration JSONs + elasticSearchSFQuestionsIndex = RESOURCE_BUNDLE.getString("elasticsearch.sf.index"); + // Real Ip header name realIpHeaderName = RESOURCE_BUNDLE.getString("http.header.realip"); String [] ips = RESOURCE_BUNDLE.getString("authentication.whitelist.ip").split(","); diff --git a/src/main/resources/simpatico.properties b/src/main/resources/simpatico.properties index 1d06695..506f94b 100644 --- a/src/main/resources/simpatico.properties +++ b/src/main/resources/simpatico.properties @@ -9,6 +9,7 @@ elasticsearch.hi.index=hi elasticsearch.shared.index=shared elasticsearch.piwik.index=piwik elasticsearch.piwik.type=piwikAnalytics +elasticsearch.sf.index=sf_questions # Piwik piwik.api_url=http://127.0.0.1:90/piwik/?module=API diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index d590cb6..dac45c7 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -77,6 +77,10 @@ NoAuthenticationFilter /api/sf/selectdialog + + NoAuthenticationFilter + /api/sf/config + AuthenticationFilter /api/* From e09cdee7d4e38b21c508008ce7b90df50033f366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20M=C3=A9ndez?= Date: Tue, 19 Jun 2018 10:49:01 +0200 Subject: [PATCH 17/23] [IMP] New class for eSM endpoints. Added few changes for Session Feedback --- .../simpatico/rest/SimpaticoResourceESM.java | 490 ++++++++++++++++++ .../simpatico/rest/SimpaticoResourceTAE.java | 31 +- .../utils/ElasticSearchConnector.java | 2 +- src/main/webapp/WEB-INF/web.xml | 4 + 4 files changed, 518 insertions(+), 9 deletions(-) create mode 100644 src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceESM.java diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceESM.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceESM.java new file mode 100644 index 0000000..26eb3a0 --- /dev/null +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceESM.java @@ -0,0 +1,490 @@ +package es.hiiberia.simpatico.rest; + +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +import org.apache.log4j.Logger; +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONObject; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.sort.SortOrder; + +import es.hiiberia.simpatico.utils.ElasticSearchConnector; +import es.hiiberia.simpatico.utils.SimpaticoProperties; + +/** + * + * @author hi + * Every endpoint returns a percentage to directly use it in eSM. + * Parameter "eserviceId" is mandatory. + * Parameters "init" and "end" are optional. + * No authentication needed to access this paths. + */ +@Path("/esm") +public class SimpaticoResourceESM { + + private static int numLinesPrintStackInternalError = 5; + private static int[] ownIDs = {10, 11}; + + @GET + @Path("/totalrequests") + public Response getTotalRequests(@QueryParam("eserviceId") String eservice, @QueryParam("init") String init, @QueryParam("end") String end) { + // Search all form_start events from init to end. If no dates present, search all + if (eservice != null) { + BoolQueryBuilder query = new BoolQueryBuilder(); + query.must(QueryBuilders.matchQuery("e-serviceID", eservice)); + query.must(QueryBuilders.matchQuery("event", "form_start")); + query.must(QueryBuilders.rangeQuery("created").gte(init).lte(end)); + // Filter out our IDS + query.mustNot(QueryBuilders.matchQuery("userID", ownIDs[0])); + query.mustNot(QueryBuilders.matchQuery("userID", ownIDs[1])); + + try { + SearchResponse responseES = ElasticSearchConnector.getInstance().searchES(SimpaticoProperties.elasticSearchSharedIndex, null, query, SimpaticoProperties.elasticSearchCreatedFieldName, SortOrder.ASC, 1); + long hits = responseES.getHits().getTotalHits(); + return Response.status(SimpaticoResourceUtils.serverOkCode).entity(hits).build(); + } catch (Exception e) { + Logger.getRootLogger().error(e.getMessage()); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + } else { + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "eserviceId parameter is mandatory"); + } + } + + @GET + @Path("/finishedrequests") + public Response getTotalFinishedRequests(@QueryParam("eserviceId") String eservice, @QueryParam("init") String init, @QueryParam("end") String end) { + // Search all form_end events from init to end. If no dates present, search all + if (eservice != null) { + BoolQueryBuilder query = new BoolQueryBuilder(); + query.must(QueryBuilders.matchQuery("e-serviceID", eservice)); + query.must(QueryBuilders.matchQuery("event", "form_end")); + query.must(QueryBuilders.rangeQuery("created").gte(init).lte(end)); + // Filter out our IDS + query.mustNot(QueryBuilders.matchQuery("userID", ownIDs[0])); + query.mustNot(QueryBuilders.matchQuery("userID", ownIDs[1])); + + try { + SearchResponse responseES = ElasticSearchConnector.getInstance().searchES(SimpaticoProperties.elasticSearchSharedIndex, null, query, SimpaticoProperties.elasticSearchCreatedFieldName, SortOrder.ASC, 1); + long hits = responseES.getHits().getTotalHits(); + return Response.status(SimpaticoResourceUtils.serverOkCode).entity(hits).build(); + } catch (Exception e) { + Logger.getRootLogger().error(e.getMessage()); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + } else { + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "eserviceId parameter is mandatory"); + } + } + + @GET + @Path("/averagetime") + public Response getAverageTime(@QueryParam("eserviceId") String eservice, @QueryParam("init") String init, @QueryParam("end") String end) { + // Search all session_end events. Get sum of fields averageTime and divide it between the number of total hits + if (eservice != null) { + BoolQueryBuilder query = new BoolQueryBuilder(); + query.must(QueryBuilders.matchQuery("e-serviceID", eservice)); + query.must(QueryBuilders.matchQuery("event", "session_end")); + query.must(QueryBuilders.rangeQuery("created").gte(init).lte(end)); + // Filter out our IDS + query.mustNot(QueryBuilders.matchQuery("userID", ownIDs[0])); + query.mustNot(QueryBuilders.matchQuery("userID", ownIDs[1])); + + try { + SearchResponse responseES = ElasticSearchConnector.getInstance().searchES(SimpaticoProperties.elasticSearchSharedIndex, null, query, SimpaticoProperties.elasticSearchCreatedFieldName, SortOrder.ASC, 0); + long hits = responseES.getHits().getTotalHits(); + int sum = 0; + for (SearchHit hit: responseES.getHits().getHits()) { + int value = (int) hit.getSource().get("averageTime"); + sum += value; + } + + long sumMin = TimeUnit.MILLISECONDS.toMinutes(sum); // Total time in minutes + long averageMin = sumMin / hits; + return Response.status(SimpaticoResourceUtils.serverOkCode).entity(averageMin).build(); + } catch (Exception e) { + Logger.getRootLogger().error(e.getMessage()); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + } else { + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "eserviceId parameter is mandatory"); + } + } + + @GET + @Path("/satisfaction") + public Response getFacesSatisfaction(@QueryParam("eserviceId") String eservice, @QueryParam("init") String init, @QueryParam("end") String end) { + // Search all session_feedback events from init to end. Return an array of 3 positions: happy, normal, sad with their respectives total values + if (eservice != null) { + BoolQueryBuilder query = new BoolQueryBuilder(); + query.must(QueryBuilders.matchQuery("e-serviceID", eservice)); + query.must(QueryBuilders.matchQuery("event", "session_feedback")); + query.must(QueryBuilders.rangeQuery("created").gte(init).lte(end)); + // Filter out our IDS + query.mustNot(QueryBuilders.matchQuery("userID", ownIDs[0])); + query.mustNot(QueryBuilders.matchQuery("userID", ownIDs[1])); + + try { + SearchResponse responseES = ElasticSearchConnector.getInstance().searchES(SimpaticoProperties.elasticSearchHIIndex, null, query, SimpaticoProperties.elasticSearchCreatedFieldName, SortOrder.ASC, 0); + JSONArray totalsAr = new JSONArray(); // happy, normal, sad + totalsAr.put(0).put(0).put(0); // initialize array + for (SearchHit hit: responseES.getHits().getHits()) { + String value = (String) hit.getSource().get("faces_session_feedback"); + if (value != null) { + // Cases + if (value.equals("happy")) { + totalsAr.put(0, totalsAr.getInt(0)+1); + } else if (value.equals("normal")) { + totalsAr.put(1, totalsAr.getInt(1)+1); + } else if (value.equals("sad")) { + totalsAr.put(2, totalsAr.getInt(2)+1); + } + } + } + return Response.status(SimpaticoResourceUtils.serverOkCode).entity(totalsAr.toString()).build(); + } catch (Exception e) { + Logger.getRootLogger().error(e.getMessage()); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + } else { + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "eserviceId parameter is mandatory"); + } + } + + @GET + @Path("/ctzuse") + public Response getUseOfCtz(@QueryParam("eserviceId") String eservice, @QueryParam("init") String init, @QueryParam("end") String end) { + if (eservice != null) { + BoolQueryBuilder query = new BoolQueryBuilder(); + query.must(QueryBuilders.matchQuery("e-serviceID", eservice)); + query.must(QueryBuilders.matchQuery("event", "simplification_start")); + query.must(QueryBuilders.matchQuery("component", "ctz")); + query.must(QueryBuilders.rangeQuery("created").gte(init).lte(end)); + // Filter out our IDS + query.mustNot(QueryBuilders.matchQuery("userID", ownIDs[0])); + query.mustNot(QueryBuilders.matchQuery("userID", ownIDs[1])); + + try { + // First get total sessions + float totalSessions = (float) getTotalSessions(eservice, init, end); + + SearchResponse responseES = ElasticSearchConnector.getInstance().searchES(SimpaticoProperties.elasticSearchHIIndex, null, query, SimpaticoProperties.elasticSearchCreatedFieldName, SortOrder.ASC, 1); + float hits = (float) responseES.getHits().getTotalHits(); + float percentage = (hits/totalSessions)*100; // calculate percentage + return Response.status(SimpaticoResourceUtils.serverOkCode).entity((int) percentage).build(); // Remove decimals from float percentage + } catch (Exception e) { + Logger.getRootLogger().error(e.getMessage()); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverInternalServerErrorCode, SimpaticoResourceUtils.internalErrorResponse + ": " + SimpaticoResourceUtils.getInternalErrorMessageWithStackTrace(e, numLinesPrintStackInternalError)); + } + } else { + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "eserviceId parameter is mandatory"); + } + } + + @GET + @Path("/ctzuseful") + public Response getHowUsefulCtzIs(@QueryParam("eserviceId") String eservice, @QueryParam("init") String init, @QueryParam("end") String end) { + if (eservice != null) { + BoolQueryBuilder query = new BoolQueryBuilder(); + query.must(QueryBuilders.matchQuery("e-serviceID", eservice)); + query.must(QueryBuilders.matchQuery("event", "session_feedback")); + query.must(QueryBuilders.rangeQuery("created").gte(init).lte(end)); + query.must(QueryBuilders.matchQuery("radios.component", "global")); // To differentiate from previous session_feedback events. New structure + // Should have at least one field with the component "ctz" + query.should(QueryBuilders.matchQuery("ranges.component", "ctz")); + query.should(QueryBuilders.matchQuery("texts.component", "ctz")); + query.should(QueryBuilders.matchQuery("radios.component", "ctz")); + query.minimumNumberShouldMatch(1); + + try { + SearchResponse responseES = ElasticSearchConnector.getInstance().searchES(SimpaticoProperties.elasticSearchHIIndex, null, query, SimpaticoProperties.elasticSearchCreatedFieldName, SortOrder.ASC, 0); + // Get the ranges values + int totalResponses = 0; // Total responses with at least one questions related to ctz + int totalValues = 0; + ArrayList list; + for (SearchHit hit: responseES.getHits().getHits()) { + list = (ArrayList) hit.getSource().get("ranges"); + JSONArray ranges = new JSONArray(list); + for (int i=0; i list; + for (SearchHit hit: responseES.getHits().getHits()) { + list = (ArrayList) hit.getSource().get("ranges"); + JSONArray ranges = new JSONArray(list); + for (int i=0; i list; + for (SearchHit hit: responseES.getHits().getHits()) { + list = (ArrayList) hit.getSource().get("ranges"); + JSONArray ranges = new JSONArray(list); + for (int i=0; i list; + for (SearchHit hit: responseES.getHits().getHits()) { + list = (ArrayList) hit.getSource().get("ranges"); + JSONArray ranges = new JSONArray(list); + for (int i=0; iNoAuthenticationFilter /api/sf/config + + NoAuthenticationFilter + /api/esm/* + AuthenticationFilter /api/* From 52a855ca894adfebbd8fd308f7f2a414ac899d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20M=C3=A9ndez?= Date: Tue, 19 Jun 2018 12:52:02 +0200 Subject: [PATCH 18/23] [ADD] Wordcloud endpoint for eSM --- .../simpatico/rest/SimpaticoResourceESM.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceESM.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceESM.java index 26eb3a0..bd72e04 100644 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceESM.java +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceESM.java @@ -473,6 +473,42 @@ public Response getHowUsefulWaeIs(@QueryParam("eserviceId") String eservice, @Qu } } + @GET + @Path("/words") + public Response getWordsInComments(@QueryParam("eserviceId") String eservice, @QueryParam("init") String init, @QueryParam("end") String end) { + if (eservice != null) { + BoolQueryBuilder query = new BoolQueryBuilder(); + query.must(QueryBuilders.matchQuery("e-serviceID", eservice)); + query.must(QueryBuilders.matchQuery("event", "session_feedback")); + query.must(QueryBuilders.rangeQuery("created").gte(init).lte(end)); + query.must(QueryBuilders.matchQuery("texts.component", "global")); // To differentiate from previous session_feedback events. New structure + + try { + SearchResponse responseES = ElasticSearchConnector.getInstance().searchES(SimpaticoProperties.elasticSearchHIIndex, null, query, SimpaticoProperties.elasticSearchCreatedFieldName, SortOrder.ASC, 0); + // Get the texts string + String allComments = ""; + ArrayList list; + for (SearchHit hit: responseES.getHits().getHits()) { + list = (ArrayList) hit.getSource().get("texts"); + JSONArray texts = new JSONArray(list); + for (int i=0; i Date: Thu, 21 Jun 2018 12:17:24 +0200 Subject: [PATCH 19/23] [ADD] Language filter when searching for SF json configuration --- .../simpatico/rest/SimpaticoResourceSF.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java index a826fa9..6c5762b 100644 --- a/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java +++ b/src/main/java/es/hiiberia/simpatico/rest/SimpaticoResourceSF.java @@ -358,15 +358,31 @@ public Response selectDialog(@QueryParam("id") String userId, @QueryParam("timeo @GET @Path("/config") @Produces(MediaType.APPLICATION_JSON) - public Response getConfig(@QueryParam("eserviceId") String eservice) { + public Response getConfig(@QueryParam("eserviceId") String eservice, @QueryParam("lang") String lang) { if (eservice == null || eservice.isEmpty()) { - return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "Query parameter eserviceId is missing"); + return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.serverBadRequestCode, "Query parameter eserviceId is mandatory"); } else { try { ArrayList words = new ArrayList<>(); words.add(eservice); SearchResponse responseES = ElasticSearchConnector.getInstance().search(SimpaticoProperties.elasticSearchSFQuestionsIndex, ES_TYPE, "common.eserviceId", words, null, null, 20); - return SimpaticoResourceUtils.createMessageResponse(SimpaticoResourceUtils.searchResponse2JSONResponse(responseES)); + JSONObject resJSON = SimpaticoResourceUtils.searchResponse2JSONResponse(responseES); + if (lang != null) { + JSONArray results = resJSON.getJSONArray("results"); + JSONObject serviceConfig = new JSONObject(); + // Get the config JSON for the correspondent lang (not possible to do it in the search) + for (int i=0; i Date: Thu, 21 Jun 2018 12:22:17 +0200 Subject: [PATCH 20/23] [IMP] Updated SF configuration pdf --- SF_Configuration.pdf | Bin 28567 -> 28929 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/SF_Configuration.pdf b/SF_Configuration.pdf index 321ce527e02dcbc32ec5ae34e9061eda06a3eee4..ede9ed9062f46f4ce4f249ffe0b1a96c8885ce89 100644 GIT binary patch delta 18389 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 Date: Wed, 6 Feb 2019 14:18:22 +0200 Subject: [PATCH 21/23] Add files via upload --- LICENSE.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..452e012 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2018 HI Iberia Ingeniería y Proyectos, S.L. +http://www.hi-iberia.es + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file From 76263c48f625edbaf3d61e867d2917983dcda398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Santos=20de=20la=20C=C3=A1mara?= Date: Wed, 6 Feb 2019 14:18:50 +0200 Subject: [PATCH 22/23] Delete LICENSE.txt --- LICENSE.txt | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 452e012..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 HI Iberia Ingeniería y Proyectos, S.L. -http://www.hi-iberia.es - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file From 56e6074513e433e33d13aa9db02676305466973a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Santos=20de=20la=20C=C3=A1mara?= Date: Wed, 6 Feb 2019 14:23:17 +0200 Subject: [PATCH 23/23] MIT License for logs --- LICENSE | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..452e012 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2018 HI Iberia Ingeniería y Proyectos, S.L. +http://www.hi-iberia.es + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file