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

Fix #5: GAE Abstraction part 1 - Topic Page handler [Blocked: #85] #78

Merged
merged 29 commits into from
Sep 10, 2019

Conversation

rt4914
Copy link
Contributor

@rt4914 rt4914 commented Aug 27, 2019

Explanation

The PR contains data module, necessary libraries for API calls and Topic Management System models, retrofit instance and interceptor.

Also this PR was earlier on #61 and #75

Libraries Used

  1. Retrofit: implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    This library is responsible for making API calls to Oppia backend
  2. Retrofit Moshi Converter: implementation 'com.squareup.retrofit2:converter-moshi:2.5.0'
    This library is the actual deserializer that we are using over GSON.
  3. Moshi Kotlin Support: implementation"com.squareup.moshi:moshi-kotlin:1.8.0"
    Moshi Kotlin support library for using Moshi in Kotlin.

Checklist

Work items:

  • Identify among all existing GAE endpoints (get/post), which we can use during the prototype
  • Loosely identify data missing from the above endpoints (this bucket is expected to be small/non-existent), and identify which endpoints have data which needs to be changed for the Android app
  • Specify the Retrofit interfaces to represent all of the identified endpoints (simple interfaces/no annotations or return types needed beyond pseudocode)
  • Identify** what data (via pseudocode data structures/YAML/JSON/etc) we will be passing along from the backend through these endpoints
  • Identify how error handling will work (e.g. what Retrofit does), whether RPC retry is supported & how it is/can be configured, etc.
  • Determine the testing strategy for the GAE endpoints for downstream app components

BenHenning and others added 4 commits August 26, 2019 20:40
High-level notes:
- Data sources are now being referred to as 'DataProviders'
- This commit also fixes the build which was broken in a previous PR
  ('compile' needs to be used for the proto library)
- The data source module is referred to as just 'data'
- This introduces a few style changes to prohibit wildcard imports
- This also normalizes the JDK versions needed across modules, potentially
  fixing some of the repeated reversions the IDE does for a Java SDK
  version setting

Functional differences: the app now properly shows the correct string upon
initial open ("Welcome to Oppia"), and reopening thereafter correctly
shows the updated string ("Welcome back to Oppia"). Rotating the device is
currently broken without Dagger integration.

Overall, this moves all general data provider functionality into the new
module, and introduces a PersistentCacheStore to store proto messages
on-disk, or load them into memory if they are available. Overall, this
commit is introducing a potentially reasonable medium-term solution for
data providers and on-disk proto storage. A long-term design should still
be determined, but this seems like a good place to start.

Tests are currently broken or missing. New TODOs will also be resolved in
a later commit.
@rt4914 rt4914 requested a review from BenHenning August 27, 2019 18:25
@rt4914 rt4914 assigned rt4914 and BenHenning and unassigned rt4914 Aug 27, 2019
@rt4914
Copy link
Contributor Author

rt4914 commented Aug 27, 2019

@BenHenning PTAL. This PR should help @veena14cs in topic-index-handler as well as I will use this branch for my future PRs for other APIs.

Copy link
Member

@BenHenning BenHenning left a comment

Choose a reason for hiding this comment

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

Thanks @rt4914! I did a full review, though I'm expecting the tests to change some per my comment about testing via public APIs.

Generally, the PR looks really good! I think one more thorough pass will be sufficient to get this checked in since I'm especially interested in seeing tests that we can use as canonical examples for all future HTTP test code. Please let me know if you have any questions, concerns, or doubts about any of my comments or the PR.

/** Constant values for data module will be defined here */
object Constants {

const val RESPONSE_SUCCESS = 200
Copy link
Member

Choose a reason for hiding this comment

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

Unless this is going to be processed outside of this file, we could just add it as a module-level constant to the interceptor.

Please also add a Javadoc to it, or name it something self documenting like HTTP_OK.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unable to understand your first line. Do you mean I should declare this constant in gradle.properties ?

Also, I have renamed RESPONSE_SUCCESS oto HTTP_OK

* @param rawJson: This is the string that we get in body of our response
* @return String: rawJson without XSSI_PREFIX
*/
fun removeXSSIPrefixFromResponse(rawJson: String): String {
Copy link
Member

Choose a reason for hiding this comment

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

Since this is a helper method, let's make it private since it shouldn't be accessed outside of this class.

When testing the interceptor, we should only test it from its public API to have parity with how other classes will interact with it. In this case, we should use intercept() to test that the XSSI prefix is properly removed.

Is there a way to use Retrofit in tests? https://riggaroo.co.za/retrofit-2-mocking-http-responses/ suggests that this is possible.

This is actually a great opportunity to demonstrate how to mock HTTP responses in Retrofit, since that will be the foundation for our later downstream fakes that we use to support the HTTP side of the entire app infrastructure for UI tests.


/**
* Interceptor on top of Retrofit to modify requests and response
* The Interceptor intercepts requests and response and in every response
Copy link
Member

Choose a reason for hiding this comment

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

Please wrap Javadoc comments to the 120 character limit, or put new lines between Javadoc paragraphs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@BenHenning PTAL.

.client(client.build())
.build()
}
return@lazy retrofit
Copy link
Member

Choose a reason for hiding this comment

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

Out of curiosity, do we actually need to use return@lazy here? If so, why? I haven't needed that yet for lazy initialization. Do you know what it does?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, in my case it was giving error if I use return retrofit and the solution for that was this.

private var retrofit: Retrofit? = null

val retrofitInstance: Retrofit? by lazy {
if (retrofit == null) {
Copy link
Member

Choose a reason for hiding this comment

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

Is this additional logic needed? Can't we just initialize Retrofit lazily? I don't completely follow why we need this dual lazy logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Check the updated code for this.


/**
* Model for serialization/deserialization using Moshi with Retrofit
* @see <a href="https://github.com/oppia/oppia/blob/b33aa9cf1aa6372e12d0b35f95cceb44efe5320f/core/domain/story_domain.py#L1118">Oppia-Web StorySummary structure</a>
Copy link
Member

Choose a reason for hiding this comment

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

Two things regarding these links:

  1. Since Kotlin uses markdown for Javadocs, you can make this a bit shorter by avoiding the href anchor tag
  2. You can shorten these blob links by just using the first 6 hash characters, e.g. https://github.com/oppia/oppia/blob/b33aa9/core/domain/story_domain.py#L1118.

Ditto elsewhere in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. I tried this @see [link](https://github.com/oppia/oppia/blob/b33aa9/core/domain/story_domain.py#L1118) , but it was not working. Can you guide me this?
  2. Changed blob links to 6 hash characters only.

import com.squareup.moshi.JsonClass

/**
* Model for serialization/deserialization using Moshi with Retrofit
Copy link
Member

Choose a reason for hiding this comment

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

Let's describe what this model actually is, rather than how it should be used. Ditto elsewhere in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed the description for all models

* @see <a href="https://github.com/oppia/oppia/blob/b33aa9cf1aa6372e12d0b35f95cceb44efe5320f/core/domain/story_domain.py#L1118">Oppia-Web StorySummary structure</a>
*/
@JsonClass(generateAdapter = true)
data class StorySummary(
Copy link
Member

Choose a reason for hiding this comment

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

I believe at some point we discussed suffixing all of these GAE models with 'Gae' to help avoid conflicts with future domain models that these will be converted through. Is that still the case? If so, let's go ahead and make that change to these model classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added Gae prefix to all models

@Json(name = "topic_name") val topicName: String?,
@Json(name = "canonical_story_dicts") val canonicalStoryDicts: List<StorySummary?>?,
@Json(name = "additional_story_dicts") val additionalStoryDicts: List<StorySummary?>?,
/** skill_descriptions map has skill id as key and skill name as value */
Copy link
Member

Choose a reason for hiding this comment

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

Consider phrasing this instead as something like:

/** A map of skill descriptions keyed by skill ID. */

That way the documentation is describing the property, and still includes the important bit about how the map is keyed & what it contains.

Ditto for degreesOfMastery below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

class NetworkInterceptorTest {

@Test
fun testNetworkInterceptor_removeXSSIPrefixFromResponse_withXSSIPREFIX() {
Copy link
Member

Choose a reason for hiding this comment

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

Nit on naming: when camel casing with fully capitalized names, you can lowercase all the letters after the first one (see https://google.github.io/styleguide/javaguide.html#s5.3-camel-case). In this case:

testNetworkInterceptor_removeXssiPrefixFromResponse_withXssiPrefix

Also, test names can be clearer when phrased as:

testAction_withOneCondition_andOtherCondition_hasExpectedOutcome

Your name is close to this, but consider the alternative:

testNetworkInterceptor_withXssiPrefix_removesXssiPrefix

If all tests are phrased this way, it will be really easy to see which behaviors are being tested which can help identify missing cases for more complex classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@BenHenning Thanks a lot. This makes sense.

@BenHenning BenHenning assigned rt4914 and unassigned BenHenning Aug 28, 2019
BenHenning and others added 6 commits August 28, 2019 00:05
This is loosely based on both
https://github.com/tfcporciuncula/dagger-simple-way and
https://dagger.dev/android.html, except this approach is meant to keep
activities and fragments as thin as possible and push as much UI
presentation and service business logic into Dagger-provided objects as
possible. This maximizes Dagger use throughout the codebase, and opens the
potential for future code generation for these thin adapter fragments &
services.

This fixes the rotation bug in the app: rotating upon the initial app open
retains the 'Welcome to Oppia' label since the same user app history
controller is used in both instances of HomeFragment due to it being
singleton scoped and not needing to be recreated.

No tests were updated with these changes.
Copy link
Contributor Author

@rt4914 rt4914 left a comment

Choose a reason for hiding this comment

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

@BenHenning I have replied to most of your comments and made changes accordingly. I will now work on Retrofit Test cases

/** Constant values for data module will be defined here */
object Constants {

const val RESPONSE_SUCCESS = 200
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unable to understand your first line. Do you mean I should declare this constant in gradle.properties ?

Also, I have renamed RESPONSE_SUCCESS oto HTTP_OK


/**
* Interceptor on top of Retrofit to modify requests and response
* The Interceptor intercepts requests and response and in every response
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@BenHenning PTAL.

object NetworkSettings {

// TODO(#74): Move this to DI graph
const val BASE_URL = "https://oppia.org"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am multiple doubts in this.

  1. DEVELOPER_URL similar to demo app? I haven't seen that anywhere.
  2. For switching I am introduing a boolean value which will switch between developer and prod server.

object NetworkSettings {

// TODO(#74): Move this to DI graph
const val BASE_URL = "https://oppia.org"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have removed BASE_URL and intoduced a fun getBaseUrl() and using boolean value this method switches between the server.

object NetworkSettings {

// TODO(#74): Move this to DI graph
const val BASE_URL = "https://oppia.org"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, not sure what should be the exact value of PROD and DEVELOPER URL for now.


/**
* Model for serialization/deserialization using Moshi with Retrofit
* @see <a href="https://github.com/oppia/oppia/blob/b33aa9cf1aa6372e12d0b35f95cceb44efe5320f/core/domain/story_domain.py#L1118">Oppia-Web StorySummary structure</a>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. I tried this @see [link](https://github.com/oppia/oppia/blob/b33aa9/core/domain/story_domain.py#L1118) , but it was not working. Can you guide me this?
  2. Changed blob links to 6 hash characters only.

* @see <a href="https://github.com/oppia/oppia/blob/b33aa9cf1aa6372e12d0b35f95cceb44efe5320f/core/domain/story_domain.py#L1118">Oppia-Web StorySummary structure</a>
*/
@JsonClass(generateAdapter = true)
data class StorySummary(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added Gae prefix to all models


/**
* Model for serialization/deserialization using Moshi with Retrofit *
* @see <a href="https://github.com/oppia/oppia/blob/b33aa9cf1aa6372e12d0b35f95cceb44efe5320f/core/controllers/topic_viewer.py#L45">Oppia-Web Topic structure</a>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it is pointing correctly because the response is a mixture of multiple responses from different function calls.

@Json(name = "topic_name") val topicName: String?,
@Json(name = "canonical_story_dicts") val canonicalStoryDicts: List<StorySummary?>?,
@Json(name = "additional_story_dicts") val additionalStoryDicts: List<StorySummary?>?,
/** skill_descriptions map has skill id as key and skill name as value */
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

class NetworkInterceptorTest {

@Test
fun testNetworkInterceptor_removeXSSIPrefixFromResponse_withXSSIPREFIX() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@BenHenning Thanks a lot. This makes sense.

@rt4914 rt4914 assigned BenHenning and unassigned rt4914 Aug 30, 2019
@rt4914
Copy link
Contributor Author

rt4914 commented Aug 30, 2019

@BenHenning PTAL

@BenHenning BenHenning changed the title Fix part of #5: GAE Abstraction part 1 - Topic Page handler Fix part of #5: GAE Abstraction part 1 - Topic Page handler [Blocked: #85] Sep 1, 2019
@BenHenning
Copy link
Member

NB: This is blocked on #85.

veena14cs and others added 6 commits September 4, 2019 18:02
* introduced topic index handler

* Call retrofit data handlers from data module

* Update topic_index_items.xml

* removed extra space

* fixed issues

* Update AndroidManifest.xml

* Removed Ui part. http handlers are included in data module

* done changes in handler and data as per the specs

* Update TopicSummarytDicts.kt

* Update NetworkSettings.kt

* added classroom datahandler

* renamed corresponding classes to classroom

* fixed issues

* Update GaeClassroom.kt

* removed spaces

* Update AndroidManifest.xml

* removed unwanted spaces

* added space
@rt4914 rt4914 changed the base branch from introduce-data-module to develop September 5, 2019 11:48
Copy link
Contributor Author

@rt4914 rt4914 left a comment

Choose a reason for hiding this comment

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

data/src/test/java/org/oppia/data/MockTopicService.kt Outdated Show resolved Hide resolved
data/src/test/java/org/oppia/data/MockTopicTest.kt Outdated Show resolved Hide resolved
private var retrofit: Retrofit? = null

@Before
@Throws(Exception::class)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did search this on internet but haven't found a proper answer for this. I have seen different resources which uses this and there are some resources which do not use this.

Even I am not sure, whether we actually need this or not.


/** Constant which defines successful API call */
const val HTTP_OK = 200

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@BenHenning Not sure how to dagger-ify this.

app/build.gradle Show resolved Hide resolved
data/build.gradle Show resolved Hide resolved
@rt4914 rt4914 assigned BenHenning and unassigned rt4914 Sep 5, 2019
Copy link
Member

@BenHenning BenHenning left a comment

Choose a reason for hiding this comment

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

Generally LGTM. Please re-assign to me if you'd like me to do another pass, but as long as you address my comments feel free to merge this.

app/build.gradle Show resolved Hide resolved
data/build.gradle Show resolved Hide resolved
data/build.gradle Show resolved Hide resolved
import javax.inject.Singleton

/**
* Sample resource https://github.com/gahfy/Feed-Me/tree/unitTests
Copy link
Member

Choose a reason for hiding this comment

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

Nit: let's just make this a separate paragraph in the below Javadoc. Currently, this is a floating Javadoc with no associated element. :)

Copy link
Member

Choose a reason for hiding this comment

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

Actually, I'm not sure where this Javadoc should go since this module isn't related to testing directly. Maybe nowhere for now?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have shifted to this javadoc because this code has been referenced from that link and therefore it is worth mentioning it here for now. And later we can remove this.


@Test
@Throws(Exception::class)
fun testMockTopicService_withTopicName_success() {
Copy link
Member

Choose a reason for hiding this comment

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

Per the name, this doesn't seem to include the actual action that's being tested. Can you update it to include the operation itself?

Also, it's often more useful to be specific about what's successful (e.g. that the response has the expected topic name). That way we can easily check multiple tests with a similar naming style for thoroughness of behaviors being tested.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed this name to testTopicService_usingFakeJson_deserializationSuccessful but still not convinced. I am finding it difficult to name the methods correctly. Will need to see multiple code samples to get proper understanding.

Copy link
Member

Choose a reason for hiding this comment

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

This seems reasonable, though you could probably make it slightly clearer by including what is successful, e.g.:

testService_withFakeJson_deserializesCorrectTopicName

@rt4914 rt4914 assigned BenHenning and unassigned rt4914 Sep 6, 2019
@rt4914
Copy link
Contributor Author

rt4914 commented Sep 6, 2019

@BenHenning PTAL

app/build.gradle Outdated Show resolved Hide resolved

@Test
@Throws(Exception::class)
fun testMockTopicService_withTopicName_success() {
Copy link
Member

Choose a reason for hiding this comment

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

This seems reasonable, though you could probably make it slightly clearer by including what is successful, e.g.:

testService_withFakeJson_deserializesCorrectTopicName

Copy link
Member

@BenHenning BenHenning left a comment

Choose a reason for hiding this comment

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

Thanks @rt4914. The recent changes LGTM; left a few comments to improve clarity on a few things.

@BenHenning BenHenning assigned rt4914 and unassigned BenHenning Sep 10, 2019
@rt4914
Copy link
Contributor Author

rt4914 commented Sep 10, 2019

This PR is complete but it does need some more test cases to be introduced which will be finished with other PRs related to services.

@rt4914 rt4914 merged commit 689e970 into develop Sep 10, 2019
@rt4914 rt4914 deleted the gae-abstraction-base branch September 13, 2019 08:27
rt4914 added a commit that referenced this pull request Sep 17, 2019
…nt System [Blocked: #78] (#102)

* Subtopic service, model and testcases

* Reformatting code

* Nit changes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants