Skip to content
This repository has been archived by the owner on Apr 4, 2023. It is now read-only.

Commit

Permalink
[SDK-569] : User profile issues fixes (#94)
Browse files Browse the repository at this point in the history
* User profile issues fixes

* Adding checks, logs and validation for user profile functions.

* typo fixes

* typo fixes

* renaming validation functions

* typo fix

* User data changes

* Rework validation functions for user profile

* Removing extra functions

* removing extra async values

* Moving all validations in one function

* small changes

* typo fixes

* added comments to user data validation function

* try/catch added in user data fucntion

* typo fix

* added info message

* typo fixes

* Adding comments

* tweaking log call

* reverting changes in cordova_example.sh

Co-authored-by: ArtursK <[email protected]>
  • Loading branch information
ijunaid and ArtursKadikis authored May 26, 2022
1 parent 0058817 commit a971ac7
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 68 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* !! Major breaking change !! Deprecating "ADVERTISING_ID" as device ID generation strategy. SDK will fall back to 'OPEN_UDID'. All "ADVERTISING_ID" device ID's will have their type changed to "OPEN_UDID". If the device will have a "null" device ID, a random one will be generated.
* !! Major breaking change !! Changing device ID without merging will now clear all consent. It has to be given again after this operation.
* !! Major breaking change !! Entering temporary ID mode will now clear all consent. It has to be given again after this operation.
* User profile issue fixed, user data was deleted for key if no value, null or undefined value was provided against that key.
* Device ID can now be changed when no consent is given
* Push notification now display/use the sent badge number in Android. It's visualization depends on the launcher.
* When recording internal events with 'recordEvent', the respective feature consent will now be checked instead of the 'events' consent.
Expand Down
193 changes: 165 additions & 28 deletions Countly.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,37 @@ Countly.setLoggingEnabled = function(isDebug = true){
}

// countly sending user data
Countly.setUserData = function(options){
Countly.setUserData = function(userData){
var message = null;
if(!userData) {
message = "User profile data should not be null or undefined";
log("setUserData", message, logLevel.ERROR);
return message;
}
if(typeof userData !== 'object'){
message = "unsupported data type of user data '" + (typeof userData) + "'";
log("setUserData", message, logLevel.WARNING);
return message;
}
var args = [];
args.push(options.name || "");
args.push(options.username || "");
args.push(options.email || "");
args.push(options.organization || "");
args.push(options.phone || "");
args.push(options.picture || "");
args.push(options.picturePath || "");
args.push(options.gender || "");
args.push(options.byear || 0);
for(var key in userData){
if (typeof userData[key] != "string" && key.toString() != "byear")
{
message = "skipping value for key '" + key.toString() + "', due to unsupported data type '" + (typeof userData[key]) + "', its data type should be 'string'";
log("setUserData", message, logLevel.WARNING);
}

}

if(userData.org && !userData.organization) {
userData.organization = userData.org;
delete userData.org;
}

if(userData.byear) {
userData.byear = userData.byear.toString();
}
args.push(userData);

cordova.exec(Countly.onSuccess,Countly.onError,"CountlyCordova","setuserdata",args);
}
Expand Down Expand Up @@ -380,42 +400,126 @@ Countly.askForNotificationPermission = function(){
cordova.exec(Countly.onSuccess,Countly.onError,"CountlyCordova","askForNotificationPermission",[]);
}

/**
* Common function to handle user data calls, print and return message if data is not valid.
* It will return message if any issue found related to data validation else return null.
* @param {String} callName : name of function from where value is validating.
* @param {String} providedKey : name of user data key.
* @param {String} providedValue : value against provided key
* @param {String} expectedValueInfo : value from 1 to 3. 1 - no value, 2 - int value, 3 - other values
* @returns
*/
userDataHandleCall = async function(callName, providedKey, providedValue = null, expectedValueInfo = 1) {
try {
log(callName, "trying to interact with user data properties [ " + providedKey + "]", logLevel.info);
var valueArray = [];

// First we try to validate the provided key value
// Provided key should not be empty, null or undefined
var message = null;
if(!providedKey) {
message = "Key should not be null, undefined or empty";
}
else if (typeof providedKey !== "string") {
message = "skipping value for 'key', due to unsupported data type '" + (typeof providedKey) + "', its data type should be 'string'";
}
if(message) {
log(callName, message, logLevel.ERROR);
return message;
}
//provided key is acceptable, store it in the value array and check values
valueArray.push(providedKey);

// if info value is 1, we don't need any value and the validation can be skipped
if (expectedValueInfo == 2 || expectedValueInfo == 3){
// if info value is 2 we expected a parsable string or number to produce an int
// if info value is 3 we expect a non empty string

// Provided value should not be null or undefined
if (providedValue === null || providedValue === undefined) {
message = "Value should not be null or undefined";
log(callName, message, logLevel.ERROR);
return message;
}

// cache the currently provided value
var keyValue = providedValue;

if(expectedValueInfo == 2) {
// Provided value should be 'number' or 'string' that is parsable to 'number'
if (typeof providedValue == "string") {
log(functionName, "unsupported data type '" + (typeof providedValue) + "', its data type should be 'number'", logLevel.WARNING);
}
else if (typeof providedValue != "number") {
message = "skipping value for 'value', due to unsupported data type '" + (typeof providedValue) + "', its data type should be 'number'";
log(callName, message, logLevel.ERROR);
return message;
}

var intValue = parseInt(providedValue);
if (isNaN(intValue)) {
message = "skipping value for 'value', due to unsupported data type '" + (typeof providedValue) + "', its data type should be 'number' or parseable to 'integer'";
log(callName, message, logLevel.ERROR);
return message;
}
//replace the cached value with the newly parsed int
keyValue = intValue.toString();
}

//add the validated key value value array together with the key
valueArray.pushValue(keyValue);
}
cordova.exec(Countly.onSuccess, Countly.onError, "CountlyCordova", "userData_"+callName, valueArray);
}
catch (e) {
log("userDataHandleCall", e.message, logLevel.ERROR);
return e.message;
}

};

Countly.userData = {};
Countly.userData.setProperty = function(keyName, keyValue){
cordova.exec(Countly.onSuccess,Countly.onError,"CountlyCordova","userData_setProperty",[keyName.toString() || "", keyValue.toString() || ""]);
userDataHandleCall("setProperty", keyName, keyValue, 3);
};

Countly.userData.increment = function(keyName){
cordova.exec(Countly.onSuccess,Countly.onError,"CountlyCordova","userData_increment",[keyName.toString() || ""]);
userDataHandleCall("increment", keyName);
};
Countly.userData.incrementBy = function(keyName, keyIncrement){
cordova.exec(Countly.onSuccess,Countly.onError,"CountlyCordova","userData_incrementBy",[keyName.toString() || "", keyIncrement.toString() || ""]);

Countly.userData.incrementBy = function(keyName, keyValue){
userDataHandleCall("incrementBy", keyName, keyValue, 2);
};
Countly.userData.multiply = function(keyName, multiplyValue){
cordova.exec(Countly.onSuccess,Countly.onError,"CountlyCordova","userData_multiply",[keyName.toString() || "", multiplyValue.toString() || ""]);

Countly.userData.multiply = function(keyName, keyValue){
userDataHandleCall("multiply", keyName, keyValue, 2);
};
Countly.userData.saveMax = function(keyName, saveMax){
cordova.exec(Countly.onSuccess,Countly.onError,"CountlyCordova","userData_saveMax",[keyName.toString() || "", saveMax.toString() || ""]);

Countly.userData.saveMax = function(keyName, keyValue){
userDataHandleCall("saveMax", keyName, keyValue, 2);
};
Countly.userData.saveMin = function(keyName, saveMin){
cordova.exec(Countly.onSuccess,Countly.onError,"CountlyCordova","userData_saveMin",[keyName.toString() || "", saveMin.toString() || ""]);

Countly.userData.saveMin = function(keyName, keyValue){
userDataHandleCall("saveMin", keyName, keyValue, 2);
};
Countly.userData.setOnce = function(keyName, setOnce){
cordova.exec(Countly.onSuccess,Countly.onError,"CountlyCordova","userData_setOnce",[keyName.toString() || "", setOnce.toString() || ""]);

Countly.userData.setOnce = function(keyName, keyValue){
userDataHandleCall("setOnce", keyName, keyValue, 3);
};

//pushUniqueValue
Countly.userData.pushUniqueValue = function(key, value){
cordova.exec(Countly.onSuccess,Countly.onError,"CountlyCordova","userData_pushUniqueValue",[key.toString() || "", value.toString() || ""]);
Countly.userData.pushUniqueValue = function(keyName, keyValue){
userDataHandleCall("pushUniqueValue", keyName, keyValue, 3);
};

//pushValue
Countly.userData.pushValue = function(type, pushValue){
cordova.exec(Countly.onSuccess,Countly.onError,"CountlyCordova","userData_pushValue",[type.toString() || "", pushValue.toString() || ""]);
Countly.userData.pushValue = function(keyName, keyValue){
userDataHandleCall("pushValue", keyName, keyValue, 3);
};

//pullValue
Countly.userData.pullValue = function(type, pullValue){
cordova.exec(Countly.onSuccess,Countly.onError,"CountlyCordova","userData_pullValue",[type.toString() || "", pullValue.toString() || ""]);
Countly.userData.pullValue = function(keyName, keyValue){
userDataHandleCall("pullValue", keyName, keyValue, 3);
};

Countly.consents = ["sessions", "events", "views", "location", "crashes", "attribution", "users", "push", "star-rating","AppleWatch"];
Expand Down Expand Up @@ -706,3 +810,36 @@ Countly.enableApm = function(){

window.Countly = Countly;
document.addEventListener("deviceready", Countly.deviceready, false);

logLevel = {"VERBOSE": "1", "DEBUG": "2", "INFO": "3", "WARNING": "4", "ERROR": "5"};
/**
* Print log if logging is enabled
* @param {String} functionName : name of function from where value is validating.
* @param {String} message : log message
* @param {String} logLevel : log level (INFO, DEBUG, VERBOSE, WARNING, ERROR)
*/
log = (functionName, message, logLevel = logLevel.DEBUG) => {
if(Countly.isDebug) {
var logMessage = "[CountlyCordova] " + functionName + ", " + message;
switch (logLevel) {
case logLevel.VERBOSE:
console.log(logMessage);
break;
case logLevel.DEBUG:
console.debug(logMessage);
break;
case logLevel.INFO:
console.info(logMessage);
break;
case logLevel.WARNING:
console.warn(logMessage);
break;
case logLevel.ERROR:
console.error(logMessage);
break;
default:
console.log(logMessage);
}
}
};

62 changes: 43 additions & 19 deletions src/android/CountlyNative.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import ly.count.android.sdk.Countly;
import ly.count.android.sdk.DeviceId;
import ly.count.android.sdk.RemoteConfig;
import ly.count.android.sdk.CountlyStarRating;
//import ly.count.android.sdk.DeviceInfo;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import android.app.Activity;
import android.content.Context;
Expand Down Expand Up @@ -177,9 +177,9 @@ public String changeDeviceId(JSONArray args){
String newDeviceID = args.getString(0);
String onServerString = args.getString(1);
if ("1".equals(onServerString)) {
Countly.sharedInstance().changeDeviceId(newDeviceID);
Countly.sharedInstance().deviceId().changeWithMerge(newDeviceID);
} else {
Countly.sharedInstance().changeDeviceId(DeviceId.Type.DEVELOPER_SUPPLIED, newDeviceID);
Countly.sharedInstance().deviceId().changeWithoutMerge(newDeviceID);
}
return "changeDeviceId success!";
}catch (JSONException jsonException){
Expand Down Expand Up @@ -471,23 +471,12 @@ public String setLoggingEnabled(JSONArray args){

public String setuserdata(JSONArray args){
try {
// Bundle bundle = new Bundle();

this.log("setuserdata", args);
Map<String, String> bundle = new HashMap<String, String>();

bundle.put("name", args.getString(0));
bundle.put("username", args.getString(1));
bundle.put("email", args.getString(2));
bundle.put("organization", args.getString(3));
bundle.put("phone", args.getString(4));
bundle.put("picture", args.getString(5));
bundle.put("picturePath", args.getString(6));
bundle.put("gender", args.getString(7));
bundle.put("byear", String.valueOf(args.getInt(8)));

Countly.userData.setUserData(bundle);
Countly.userData.save();
JSONObject userData = args.getJSONObject(0);

Map<String,Object> userDataMap = toMap(userData);
Countly.sharedInstance().userProfile().setProperties(userDataMap);
Countly.sharedInstance().userProfile().save();
return "setuserdata success";
}catch (JSONException jsonException){
return jsonException.toString();
Expand Down Expand Up @@ -961,6 +950,11 @@ public String presentFeedbackWidget(JSONArray args, final Callback theCallback){
presentableFeedback.type = FeedbackWidgetType.valueOf(widgetType);
presentableFeedback.name = widgetName;
Countly.sharedInstance().feedback().presentFeedbackWidget(presentableFeedback, activity, closeButtonText, new FeedbackCallback() {
@Override
public void onClosed() {
// TODO : send on close result to react native JS side
}

@Override
public void onFinished(String error) {
if(error != null) {
Expand Down Expand Up @@ -1121,5 +1115,35 @@ public void onHostPause() {
}
}

public static Map<String, Object> toMap(JSONObject jsonobj) throws JSONException {
Map<String, Object> map = new HashMap<>();
Iterator<String> keys = jsonobj.keys();
while (keys.hasNext()) {
String key = keys.next();
Object value = jsonobj.get(key);
if (value instanceof JSONArray) {
value = toList((JSONArray) value);
} else if (value instanceof JSONObject) {
value = toMap((JSONObject) value);
}
map.put(key, value);
}
return map;
}

public static List<Object> toList(JSONArray array) throws JSONException {
List<Object> list = new ArrayList<>();
for (int i = 0; i < array.length(); i++) {
Object value = array.get(i);
if (value instanceof JSONArray) {
value = toList((JSONArray) value);
} else if (value instanceof JSONObject) {
value = toMap((JSONObject) value);
}
list.add(value);
}
return list;
}


}
Loading

0 comments on commit a971ac7

Please sign in to comment.