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

Changes room hierarchy endpoint to stable #5443

Merged
merged 19 commits into from Mar 11, 2022

Conversation

ericdecanini
Copy link
Contributor

@ericdecanini ericdecanini commented Mar 7, 2022

Type of change

  • Feature
  • Bugfix
  • Technical
  • Other :

Content

Adds stable room hierarchy endpoint with a fallback to the unstable one

Motivation and context

Closes #4462

Screenshots / GIFs

N/A

Tests

Testing Normal Functionality

  1. Login with any account belonging to spaces, preferrably one with subspaces too
  2. Open the home navigation drawer and see that you can see your spaces and subspaces as normal

Testing Fallback

  • Set debug points in ResolveSpaceInfoTask lines 49 and 51
  • Force a fail on the stable request (either via code or via a network interceptor) and see that it falls back to the unstable API

Tested devices

  • Physical
  • Emulator
  • OS version(s): Android 10

Checklist

@github-actions
Copy link

github-actions bot commented Mar 7, 2022

Unit Test Results

  98 files  +2    98 suites  +2   1m 16s ⏱️ +3s
174 tests +2  174 ✔️ +2  0 💤 ±0  0 ±0 
568 runs  +4  568 ✔️ +4  0 💤 ±0  0 ±0 

Results for commit 7226864. ± Comparison against base commit bdc9bc0.

♻️ This comment has been updated with latest results.

@ericdecanini ericdecanini marked this pull request as ready for review March 9, 2022 09:54
@ericdecanini ericdecanini requested review from a team and ouchadam and removed request for a team March 9, 2022 09:55
override suspend fun execute(params: ResolveSpaceInfoTask.Params): SpacesResponse {
return executeRequest(globalErrorReceiver) {

private lateinit var params: ResolveSpaceInfoTask.Params
Copy link
Contributor

Choose a reason for hiding this comment

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

we can avoid the mutable state by chaining the params into the getStableSpaceHierarchy/getUnstableSpaceHierarchy

there's also an argument for avoiding lateinit in places that don't have a strictly documented lifecycle as it can cause unexpected side effects on order changes, eg if the this.params = params was set after getSpaceHierarchy

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 considered chaining params as an argument into these private functions but deliberately chose to make params as a lateinit instance variable. My reasoning is as follows:

  1. The class is very cohesive (a.k.a its instance variables are used completely across the class), thus making its intended purpose clearer
  2. It reduces the cognitive effort of reading each function. getSpaceHierarchy() is much easier to read than getSpaceHierarchy(params) where params is repeated across every function
  3. I'm aware that a slight change in ordering would cause this to crash, but the contents and intent of the class is clear enough that a contributor shouldn't be led to making this mistake

It's a step I think is explained quite well in Uncle Bob's Clean Code, but if we still think it would be better to do otherwise then I'm happy to change it

Copy link
Contributor

Choose a reason for hiding this comment

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

I would lean towards the introduction of mutability and state to the class as cognitively heavier than adding function parameters. As soon as we introduce mutability, threading and ordering become sources of bugs, things that we can safely avoid by keeping our logic stateless (ignoring our potentially stateful class dependencies 😅)

override suspend fun execute(params: ResolveSpaceInfoTask.Params): SpacesResponse {
    return executeRequest(globalErrorReceiver) {
        try {
            spaceApi.getSpaceHierarchy(params)
        } catch (error: Throwable) {
            spaceApi.getSpaceHierarchyUnstable(params)
        }
    }
}

private suspend fun SpaceApi.getSpaceHierarchy(params: ResolveSpaceInfoTask.Params) = this.getSpaceHierarchy(
        spaceId = params.spaceId,
        suggestedOnly = params.suggestedOnly,
        limit = params.limit,
        maxDepth = params.maxDepth,
        from = params.from
)

private suspend fun SpaceApi.getSpaceHierarchyUnstable(params: ResolveSpaceInfoTask.Params) = this.getSpaceHierarchyUnstable(
        spaceId = params.spaceId,
        suggestedOnly = params.suggestedOnly,
        limit = params.limit,
        maxDepth = params.maxDepth,
        from = params.from
)

with that said, for this class I don't have too strong of an opinion as the scope is restricted and it's just 1 property, I would personally avoid vars and lateinits unless they serve a functional purpose as kotlin has other ways to reduce boilerplate

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair enough. I pushed the change!

import org.matrix.android.sdk.test.fakes.FakeSpaceApi

@ExperimentalCoroutinesApi
class DefaultResolveSpaceInfoTaskTest {
Copy link
Contributor

Choose a reason for hiding this comment

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

thanks for adding these and all the setup 💯


@Test
fun `given stable endpoint works, when execute, then return stable api data`() = runBlockingTest {
spaceApi.givenStableEndpointWorks()
Copy link
Contributor

Choose a reason for hiding this comment

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

not a strong opinion just a personal preference, it can be helpful from the test standpoint to be able to relate the inputs and results

// give stable endpoint returns a response, when execute, 
spaceApi.givenStableEndpointReturns(A_RESPONSE)

val result = resolveSpaceInfoTask.execute(spaceApi.params)

result shouldBeEqualTo A_RESPONSE

I'm wondering there's a way to relate to get the best of both worlds (with the boilerplate reduction and tightening the relationship between the given and the expected result)

fakeSpaceApi.givenStableEndpointSuccess()

val result = resolveSpaceInfoTask.execute(spaceApi.params)

result shouldBeEqualTo fakeSpaceApi.successResponse

what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tbh I like the first option as it gives the test class more flexibility about the types of responses it can test with but still maintains a very readable front. I'll go with that!

Copy link
Member

@bmarty bmarty left a comment

Choose a reason for hiding this comment

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

My 2 cents about 404.
Thanks for adding the tests!


private suspend fun getSpaceHierarchy() = try {
getStableSpaceHierarchy()
} catch (e: Throwable) {
Copy link
Member

Choose a reason for hiding this comment

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

I would check only 404 here. Other error (such as no network etc.) could be propagated directly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we want to check only for 404 specifically? Personally I think it would also be good to fallback even in the case of 500 (internal server error) and such, thus catch a simple HttpException

@bmarty
Copy link
Member

bmarty commented Mar 9, 2022

Also have you checked:

@ericdecanini
Copy link
Contributor Author

Also have you checked:

I saw the second comment just now. I'm a bit confused with the wording, but I'm guessing this is saying we want to be using join_rule? In this case we're good

order = content.order,
viaServers = content.via.orEmpty(),
activeMemberCount = summary.numJoinedMembers,
parentRoomId = spaceId,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bmarty please pay more attention to this line. This is where {children_state event}.roomId was being used. I can only assume its intended use was to point to the space id? Please correct me if I'm wrong. I don't have a great understanding of spaces

Edit: Spoke to @clokep to get more info on this. I believe this to be correct

Copy link
Member

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

@ouchadam ouchadam left a comment

Choose a reason for hiding this comment

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

I'm not too familiar with the spaces side of things when it comes to the IDs, will delegate to @bmarty for that (or maybe @fedrunov can help out!)

otherwise LGTM 💯

@ericdecanini
Copy link
Contributor Author

FYI join_rule is already being used as intended in the app

Copy link
Member

@bmarty bmarty left a comment

Choose a reason for hiding this comment

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

Some remark on the code style, else LGTM (I defer the logic about space to other people)


private suspend fun getSpaceHierarchy(params: ResolveSpaceInfoTask.Params) = try {
getStableSpaceHierarchy(params)
} catch (e: HttpException) {
Copy link
Member

Choose a reason for hiding this comment

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

Why not using HttpException. I would have checked for a rather more expected 404, but 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.

I think it would be good to protect ourselves from other server related errors (e.g. 500 - internal server error) and fallback to the unstable api in such cases. Anyway happy to discuss this before merging

getSpaceHierarchy(params)
}

private suspend fun getSpaceHierarchy(params: ResolveSpaceInfoTask.Params) = try {
Copy link
Member

Choose a reason for hiding this comment

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

I found using = try and spilling into 2 methods less readable than a simple

override suspend fun execute(params: ResolveSpaceInfoTask.Params) {
    executeRequest(globalErrorReceiver) {
        try {
            getUnstableSpaceHierarchy(params)
        } catch (e: HttpException) {
            getStableSpaceHierarchy(params)
        }
    }
}

Also I would rename getStableSpaceHierarchy to getSpaceHierarchy (rule: never use stable, just use unstable, intended to be removed after a while)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good shout!

Copy link
Member

@bmarty bmarty left a comment

Choose a reason for hiding this comment

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

Thanks for the update! I let you merge the PR?
Edit I do it

@bmarty bmarty merged commit c89554c into develop Mar 11, 2022
@bmarty bmarty deleted the task/eric/stable-hierarchy-endpoint branch March 11, 2022 16:05
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.

Use the stable hierarchy endpoint from MSC2946
3 participants