Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Multiple User Agents #19

Merged
merged 18 commits into from
Dec 13, 2017
Merged

Conversation

electrostat
Copy link
Contributor

Use a public facing api to update the user agent for telemetry events. Validate this user agent and pass along to the Telemetry Client for sending along with events.

- create public facing call to retrieve user agent
- validate given useragent
- set new user agent within telemetry client for sending along with events data
Copy link
Contributor

@Guardiola31337 Guardiola31337 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should validate both the accessToken and the userAgent when creating an instance of MapboxTelemetry and throw an error if any of them are not valid 👀
https://github.com/mapbox/mapbox-java/blob/3a4afbd88668224303360278ef927f26cd0f91ed/mapbox/libandroid-telemetry/src/main/java/com/mapbox/services/android/telemetry/MapboxTelemetry.java#L163-L168

We should also manage the generation of a full user agent within the library (TelemetryClient sounds like a good place to include that functionality) 👀
https://github.com/mapbox/mapbox-java/blob/3a4afbd88668224303360278ef927f26cd0f91ed/mapbox/libandroid-telemetry/src/main/java/com/mapbox/services/android/telemetry/MapboxTelemetry.java#L313-L316

@@ -35,6 +35,10 @@ public TelemetryClient(String accessToken, String userAgent, TelemetryClientSett
this.logger = logger;
}

void setUserAgent(String userAgent) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get/set nomenclature is hijacked by the Java language and should be only used when defining POJOs/DTOs. What about updateUserAgent instead?

@@ -277,4 +289,21 @@ public void onServiceDisconnected(ComponentName className) {
serviceBound = false;
}
};

public void newUserAgent(String userAgent) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT updateUserAgent sounds better IMO.

- rename methods
- TelemetryException
- full user agent creation
- update tests to handle method changes
Copy link
Contributor

@Guardiola31337 Guardiola31337 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great.

Some minor details.
We should also include some unit tests around the new user agent behavior/functionality.

@@ -124,6 +136,12 @@ IntentFilter obtainEventReceiverIntentFilter() {
}

private void initializeTelemetryClient(String accessToken, String userAgent) {
if (TextUtils.isEmpty(accessToken) || TextUtils.isEmpty(userAgent)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're already checking ☝️ (👀 isTelemetryClientInitialized()). What about fixing isTelemetryClientInitialized() check?

@@ -124,6 +136,12 @@ IntentFilter obtainEventReceiverIntentFilter() {
}

private void initializeTelemetryClient(String accessToken, String userAgent) {
if (TextUtils.isEmpty(accessToken) || TextUtils.isEmpty(userAgent)) {
throw new TelemetryException(
"Please, make sure you provide a valid access token and user agent."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT Magic number

private String createFullUserAgent(String userAgent) {
String appIdentifier = TelemetryUtils.getApplicationIdentifier(context);
String fullUserAgent = TextUtils.isEmpty(appIdentifier) ? userAgent : Util.toHumanReadableAscii(
String.format(TelemetryUtils.DEFAULT_LOCALE, "%s %s", appIdentifier, userAgent));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT Magic number

public static String getApplicationIdentifier(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return String.format(DEFAULT_LOCALE, "%s/%s/%s", context.getPackageName(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT Magic numbers

@@ -26,13 +30,20 @@
private String userAgent = null;
private final TelemetryClientSettings setting;
private final Logger logger;
private Context context;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#19 (review)

TelemetryClient sounds like a good place to include that functionality

If we need to pass in a new dependency (Context), I'd rather leave it as it was and expose a method (createFullUserAgent) within TelemetryUtils. Sorry if I didn't make myself clear.

@@ -21,4 +25,14 @@ static String obtainUniversalUniqueIdentifier() {
String universalUniqueIdentifier = UUID.randomUUID().toString();
return universalUniqueIdentifier;
}

public static String getApplicationIdentifier(Context context) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about exposing a createFullUserAgent method instead? ☝️

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get/set nomenclature is hijacked by the Java language and should be only used when defining POJOs/DTOs.

- cleaned up magic numbers
- moved create full user agent from TelemetryClient -> TelemetryUtils
- updated tests
- changed check and exception throw
- fix missing userAgent for tests to pass
- fix checkstyle error
Copy link
Contributor

@Guardiola31337 Guardiola31337 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we're now throwing an exception if the user passes in not valid inputs (accessToken and userAgent) I guess that

check is not needed anymore because at the time of calling sendEvents both accessToken and userAgent must be valid (check is done at MapboxTelemetry creation time) so it should be avoided/removed.

We should also include some unit tests around the "failing" cases (i.e. accessToken equals null or empty, userAgent equals null or empty, MapboxTelemetryException, etc.).

import java.util.List;

import okhttp3.Callback;

import static com.mapbox.services.android.telemetry.EventReceiver.EVENT_RECEIVER_INTENT;

public class MapboxTelemetry implements FullQueueCallback, EventCallback {
private final String ACCESS_TOKEN_USER_AGENT_EXCEPTION = "Please, make sure you provide a valid access token and user"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If ACCESS_TOKEN_USER_AGENT_EXCEPTION is a constant, we're missing the static modifier here.

if (isTelemetryClientInitialized()) {
telemetryClient = createTelemetryClient(accessToken, userAgent);
queue.setTelemetryInitialized(true);
} else {
throw new TelemetryException(ACCESS_TOKEN_USER_AGENT_EXCEPTION);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not using a guard clause instead?
Meaning throwing TelemetryException in the exceptional case (i.e. the telemetry client can't be initialized because either accessToken or userAgent are not valid) and removing the else clause (we don't need it because throwing an exception breaks the execution). IMO it makes clearer the intention of the method.


public void updateUserAgent(String userAgent) {
if (isUserAgentValid(userAgent)) {
TelemetryUtils.createFullUserAgent(userAgent, context);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is currently doing nothing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case, we should also update the telemetry client with the new userAgent so everything behaves properly.

}
}

private boolean isTelemetryClientInitialized() {
return accessToken != null && !accessToken.isEmpty();
return accessToken != null && !accessToken.isEmpty() && !userAgent.isEmpty() && userAgent != null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!userAgent.isEmpty() would cause a NullPointerException 💥 if userAgent is null (order matters).

public MapboxTelemetry(Context context, String accessToken, String userAgent, Callback httpCallback) {
this.context = context;
this.accessToken = accessToken;
this.userAgent = userAgent;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should break the execution as soon as the user passes in not valid inputs (accessToken and userAgent).
In order to avoid assigning/creating objects that might not be used, the check should be done at the very beginning whereas the assignment/creating after (because of that it might be interesting to pass in both accessToken and userAgent to the method in charge of doing that check as it was implemented before).

class TelemetryUtils {
private static final String DATE_AND_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
private static final SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_AND_TIME_PATTERN, Locale.US);
public static final Locale DEFAULT_LOCALE = Locale.US;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why DEFAULT_LOCALE needs to be public?

try {
String packageName = context.getPackageName();
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
String appIdentifier = String.format(DEFAULT_LOCALE, "%s/%s/%s", packageName,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT Magic number (Strings are also literals)
Further information: Replace Magic Number with Symbolic Constant.


return appIdentifier;
} catch (Exception exception) {
return "";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT Magic number


public static String createFullUserAgent(String userAgent, Context context) {
String appIdentifier = TelemetryUtils.obtainApplicationIdentifier(context);
String newUserAgent = Util.toHumanReadableAscii(String.format(DEFAULT_LOCALE, "%s %s", appIdentifier, userAgent));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT Magic number

EventsQueue mockedEventsQueue = mock(EventsQueue.class);
TelemetryClient mockedTelemetryClient = mock(TelemetryClient.class);
Callback mockedHttpCallback = mock(Callback.class);
SchedulerFlusher mockedSchedulerFlusher = mock(SchedulerFlusher.class);
Clock mockedClock = mock(Clock.class);
LocalBroadcastManager mockedLocalBroadcastManager = mock(LocalBroadcastManager.class);
MapboxTelemetry theMapboxTelemetry = new MapboxTelemetry(mockedContext, aValidAccessToken, mockedEventsQueue,
mockedTelemetryClient, mockedHttpCallback, mockedSchedulerFlusher, mockedClock, mockedLocalBroadcastManager);
MapboxTelemetry theMapboxTelemetry = new MapboxTelemetry(mockedContext, aValidAccessToken, aValidAccessToken,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 we're using aValidAccessToken as a user agent instead of aValidUserAgent.

- renamed and reworked isAccessTokenAndUserAgentValid()
- set telemetry client user agent when updated
- magic numbers
- cleaned up and added tests
- removed redundant tests (throws exception before test runs)
public MapboxTelemetry(Context context, String accessToken, String userAgent, Callback httpCallback) {
isAccessTokenAndUserAgentValid(accessToken, userAgent);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is checked twice, here and then within initializeTelemetryClient() 👇

private boolean isTelemetryClientInitialized() {
return accessToken != null && !accessToken.isEmpty();
boolean isAccessTokenAndUserAgentValid(String accessToken, String userAgent) throws TelemetryException {
if (isAccessToeknValid(accessToken) && isUserAgentValid(userAgent)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT Typo isAccessToeknValid

}

private boolean isAccessToeknValid(String accessToken) {
return accessToken != null && !TextUtils.isEmpty(accessToken);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going to use TextUtils.isEmpty, accessToken != null is not needed.

@@ -35,6 +34,10 @@ public TelemetryClient(String accessToken, String userAgent, TelemetryClientSett
this.logger = logger;
}

public void setUserAgent(String userAgent) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need to be public?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get/set nomenclature is hijacked by the Java language and should be only used when defining POJOs/DTOs.


public void updateUserAgent(String userAgent) {
if (isUserAgentValid(userAgent)) {
telemetryClient.setUserAgent(TelemetryUtils.createFullUserAgent(userAgent, context));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not updating userAgent.

}

private boolean isTelemetryClientInitialized() {
return accessToken != null && !accessToken.isEmpty();
boolean isAccessTokenAndUserAgentValid(String accessToken, String userAgent) throws TelemetryException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does throws TelemetryException is needed?

- rework accesstoken + useragent check
- method order
- reinstate check for sending events
- update tests
- create updateUserAgent test
- fixed bug with createFullUserAgent regarding spacing being off
- fixed 2 tests that are broken with exception refactor
- additional user agent tests
- rework: checkRequiredParameters, isAccessTokenValid,  isUserAgentValid,
- bign back queue tests
- cleanup code
Copy link
Contributor

@Guardiola31337 Guardiola31337 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comments before merging this PR.

* A {@code TelemetryException} is thrown by when MapboxTelemetry checks and finds that
* telemetry has not been properly configured.
*/
public class TelemetryException extends RuntimeException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Class 'TelemetryException' is never used

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be removed.

}

private boolean isTelemetryClientInitialized() {
return accessToken != null && !accessToken.isEmpty();
private void initializeTelemetryClient() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should revisit this because we could take advantage of the checkRequiredParameters check to not create the telemetry client and only do that if both the accessToken and the userAgent are valid so we don’t assign memory if not needed but carefully because we should still be calling queue.setTelemetryInitialized(true) in order to keep queuing events although we don’t send them.

@@ -277,4 +299,25 @@ public void onServiceDisconnected(ComponentName className) {
serviceBound = false;
}
};

private boolean isUserAgentValid(String userAgent) {
if (userAgent != null && !TextUtils.isEmpty(userAgent)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we’re going to use TextUtils.isEmpty the null check is not needed, did you 👀 what TextUtils.isEmpty does internally?
If you check the implementation the only thing is doing is blablah != null && !blablah.isEmpty(), so I’d rather remove TextUtils (Android class that could affect Unit tests) and manage it by ourselves.

}

private boolean isAccessTokenValid(String accessToken) {
if (accessToken != null && !TextUtils.isEmpty(accessToken)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here ☝️

@@ -309,6 +337,7 @@ public void checksFlusherUnregisteringWhenDisabled() throws Exception {
public void checksOnFullQueueSendEventsNotCalledWhenNullTelemetryClient() throws Exception {
Context mockedContext = mock(Context.class, RETURNS_DEEP_STUBS);
String nullAccessToken = null;
String aValidUserAgent = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a valid user agent.

mockedLocalBroadcastManager);
theMapboxTelemetry.enable();

String testUserAgent = "mapbox-navigation-android/";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comments as checksUserAgentTelemetryAndroid test.

mockedLocalBroadcastManager);
theMapboxTelemetry.enable();

String testUserAgent = "mapbox-navigation-ui-android/";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comments as checksUserAgentTelemetryAndroid test.

mockedLocalBroadcastManager);
theMapboxTelemetry.enable();

String testUserAgent = "MapboxEventsAndroid/";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comments as checksUserAgentTelemetryAndroid test.

mockedLocalBroadcastManager);
theMapboxTelemetry.enable();

String aInvalidUserAgent = "invalidUserAgent";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comments as checksUserAgentTelemetryAndroid test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not using aValidUserAgent? aValidUserAgent is redundant. We should fix the name if we want to remark something around the access token variable. aInvalidUserAgent is 👍

mockedLocalBroadcastManager);
theMapboxTelemetry.enable();

String aNullUserAgent = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comments as checksUserAgentTelemetryAndroid test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not using aValidUserAgent? aValidUserAgent is redundant. We should fix the name if we want to remark something around the access token variable. aNullUserAgent is 👍

- tweaked tests
- created internal isEmpty() method to fix test failures
- prevent telem client creation, if 'checkRequiredParameters' fails.
Copy link
Contributor

@Guardiola31337 Guardiola31337 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor minor comments and this PR should be good to go 🚀

import java.util.List;

import okhttp3.Callback;

import static com.mapbox.services.android.telemetry.EventReceiver.EVENT_RECEIVER_INTENT;

public class MapboxTelemetry implements FullQueueCallback, EventCallback {
private static final String ACCESS_TOKEN_USER_AGENT_EXCEPTION = "Please, make sure you provide a valid access token"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Private field 'ACCESS_TOKEN_USER_AGENT_EXCEPTION' is never used

We should remove it.

* A {@code TelemetryException} is thrown by when MapboxTelemetry checks and finds that
* telemetry has not been properly configured.
*/
public class TelemetryException extends RuntimeException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be removed.

}

private TelemetryClient createTelemetryClient(String accessToken, String userAgent) {
if (!checkRequiredParameters(accessToken, userAgent)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're doing the same exact check before 👀


What about extracting queue.setTelemetryInitialized(true) from initializeTelemetryClient() so we decide if creating the telemetryClient or not at construction time so we could remove this check from here and avoid to return null (which is generally a bad idea)?

mockedLocalBroadcastManager);
theMapboxTelemetry.enable();

boolean accessTokentBool = theMapboxTelemetry.checkRequiredParameters(aValidAccessToken, aValidUserAgent);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 #19 (comment)
What about validRequiredParameters? The name of the test is also giving us a hint.

mockedLocalBroadcastManager);
theMapboxTelemetry.enable();

String nullAccessToken = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mockedLocalBroadcastManager);
theMapboxTelemetry.enable();

boolean checkRequiredParameters = theMapboxTelemetry.checkRequiredParameters(aValidAccessToken, aValidUserAgent);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as in checksValidAccessTokenValidUserAgent test. The name of the test is also giving us a hint.

mockedLocalBroadcastManager);
theMapboxTelemetry.enable();

boolean checkRequiredParameters = theMapboxTelemetry.checkRequiredParameters(aValidAccessToken, aValidUserAgent);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as in checksValidAccessTokenValidUserAgent test. The name of the test is also giving us a hint.

theMapboxTelemetry.enable();

String aInvalidUserAgent = "invalidUserAgent";
boolean checkRequiredParameters = theMapboxTelemetry.checkRequiredParameters(aValidAccessToken, aInvalidUserAgent);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as in checksValidAccessTokenValidUserAgent test. The name of the test is also giving us a hint.

mockedLocalBroadcastManager);
theMapboxTelemetry.enable();

String aInvalidUserAgent = "invalidUserAgent";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not using aValidUserAgent? aValidUserAgent is redundant. We should fix the name if we want to remark something around the access token variable. aInvalidUserAgent is 👍

mockedLocalBroadcastManager);
theMapboxTelemetry.enable();

String aNullUserAgent = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not using aValidUserAgent? aValidUserAgent is redundant. We should fix the name if we want to remark something around the access token variable. aNullUserAgent is 👍

- remove unneded exception
- rework 'initializeTelemetryClient' and its implementation
- update tests
- remove unused method
- update tests
- initialize queue
Copy link
Contributor

@Guardiola31337 Guardiola31337 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from this NIT comment this PR should be good to 🚢

Well done!

@@ -35,22 +38,41 @@
private boolean isOpted = false;
private boolean serviceBound = false;

private static final String EVENTS_USERAGENT = "MapboxEventsAndroid/";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT The IDE warns us that might be a typo in EVENTS_USERAGENT. Could we separate USERAGENT with an underscore into USER_AGENT (Java's constants standard)? Same for the rest of the constants 👇

- renamed constant (added underscore between compound word) to meet java standards
- removal of spaces added when merging
@electrostat electrostat merged commit 280da39 into master Dec 13, 2017
@electrostat electrostat deleted the aa-multiple-user-agent-support branch December 13, 2017 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants