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

Track loading states of providers #9914

Merged
merged 5 commits into from
Apr 17, 2020
Merged

Conversation

felixfbecker
Copy link
Contributor

@felixfbecker felixfbecker commented Apr 16, 2020

Fixes #9349, fixes #9350. Depends on sourcegraph/codeintellify#251.

We considered the providers loading in the time between the invocation and the first emission. This is a problem, because if no providers were registered yet at invocation time, an empty result will be emitted. When providers are then registered afterwards and start loading, we don't show a loading indicator, because we had no way of knowing they were loading. It was supposed to be solved by using higher-order Observables (Observable<Observable<T>>), but they weren't piped through the whole stack, but flattened relatively quickly. I decided on the explicit object wrapper type instead.

This uses the MaybeLoadingResult<T> type added in sourcegraph/codeintellify#251 and updates the loading logic to use the overhauled, abstracted emitLoading() operator from that PR. This allows us to track throughout our stack when a result stream starts loading again. This is then used in the definition logic, passed to codeintellify for the hover logic, and also for locations in the reference panel (so unregistering/reregistering a provider should now show a loader again @sourcegraph/code-intel). The "loading again" indication is not exposed through the extension API, but that could now easily be added if we have the use case (@sourcegraph/code-intel ?).

I updated all the Rx marble tests to conform to the new emission type, and added new tests that specifically test the previously missing cases of registering a provider after an empty result was already emitted. I did my best to add comments everywhere to explain them better. I also tested manually and ran the hover e2e tests.

A side note: Updating all these tests was a lot of effort (I spent Monday-Thursday on this PR, with tests taking up more than 1 full day). There are a lot of tests here that test redundant things because implementations are not abstracted well (e.g. getHover() and getLocations() do almost the same thing). Some tests also cover a lot of cases of handling null, even though there is no functional difference between a null result and an empty array result. I partly removed the null type in favor of empty arrays, but the providers from the extension API still are allowed to return null. Because the implementation is more akin to a nested call graph as opposed to individual functions that are composed at the top level, the tests for the top-level functions still have to test this low-level behaviour. Lots of obvious potential for cleanup. As we invest more into our extension API, I foresee these code smells to slow us down the same way.

cc @sourcegraph/code-intel @beyang

@felixfbecker felixfbecker added team/web extensions Sourcegraph extensions bug An error, flaw or fault that produces an incorrect or unexpected result, or behavior. labels Apr 16, 2020
@felixfbecker felixfbecker marked this pull request as ready for review April 16, 2020 11:54
@felixfbecker felixfbecker requested a review from a team April 16, 2020 11:54
Copy link
Contributor

@lguychard lguychard left a comment

Choose a reason for hiding this comment

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

LGTM. I like the flattening of the observables returned by getLocation(), actually much easier to reason about downstream. This is a very large diff so I tried to read thoroughly but may have missed some things!

@@ -356,8 +357,6 @@ function initCodeIntelligence({
} {
const subscription = new Subscription()

const { getHover } = createLSPFromExtensions(extensionsController)
Copy link
Contributor

Choose a reason for hiding this comment

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

I love that we're deleting this 😄

shared/src/api/client/services/location.ts Outdated Show resolved Hide resolved
// Merge partial updates
scan(
(previous, current) => ({ ...previous, ...current }),
((): MaybeLoadingResult<UIDefinitionURL | null> => ({ isLoading: true, result: 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 is the IIFE necessary here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without it, the scan()'s output type parameter R would be inferred to { isLoading: boolean, result: null }, because it is inferred from the seed parameter (if provided). This is just a type hint for the actual type. Alternatives would be to specify the generic type parameters <T, R> but that would require repeating the type twice (because T = R), or using a cast, but that is unsafe. I know IIFE can look overly complex at first but it's actually the simplest & safest solution 😄

Copy link
Member

Choose a reason for hiding this comment

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

otherwise, a emptyLoadingResult variable should also work, this is indeed the simplest inline solution, but it's rather hard to understand on first sight, no? 😁

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that would work, I just wanted to avoid the definition being far away from the usage (it would have to be at the top of the file). My hope is that by using this pattern more often in the codebase it'll feel less alien and it's just one of those things that look odd at first but you get used to it quickly. But if you both feel strongly this impedes readability let me know and I'm happy to change it to the <T, R> generics solution (which is possible here, but not everywhere)

*
* @param constant The value to compare to.
*/
export const isExactly = <T, C extends T>(constant: C) => (value: T): value is C => value === constant
Copy link
Member

Choose a reason for hiding this comment

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

I love all these helpers 😁

// Merge partial updates
scan(
(previous, current) => ({ ...previous, ...current }),
((): MaybeLoadingResult<UIDefinitionURL | null> => ({ isLoading: true, result: null }))()
Copy link
Member

Choose a reason for hiding this comment

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

otherwise, a emptyLoadingResult variable should also work, this is indeed the simplest inline solution, but it's rather hard to understand on first sight, no? 😁

felixfbecker and others added 2 commits April 16, 2020 22:28
Co-Authored-By: Loïc Guychard <loic@sourcegraph.com>
@codecov
Copy link

codecov bot commented Apr 17, 2020

Codecov Report

Merging #9914 into master will increase coverage by 0.00%.
The diff coverage is 79.51%.

@@           Coverage Diff           @@
##           master    #9914   +/-   ##
=======================================
  Coverage   42.76%   42.76%           
=======================================
  Files        1347     1347           
  Lines       73949    73929   -20     
  Branches     6641     6625   -16     
=======================================
- Hits        31625    31617    -8     
+ Misses      39464    39452   -12     
  Partials     2860     2860           
Flag Coverage Δ
#unit 42.76% <79.51%> (+<0.01%) ⬆️
Impacted Files Coverage Δ
...r/src/libs/code_intelligence/code_intelligence.tsx 59.19% <0.00%> (-0.37%) ⬇️
shared/src/api/client/api/views.ts 68.00% <0.00%> (ø)
web/src/backend/features.ts 0.00% <0.00%> (ø)
web/src/repo/blob/panel/BlobPanel.tsx 0.00% <0.00%> (ø)
...ared/src/panel/views/HierarchicalLocationsView.tsx 76.47% <75.00%> (-1.03%) ⬇️
shared/src/api/client/services/location.ts 95.65% <85.71%> (+0.19%) ⬆️
shared/src/hover/HoverOverlay.tsx 62.31% <87.50%> (-1.20%) ⬇️
shared/src/hover/actions.ts 89.85% <93.93%> (+0.30%) ⬆️
browser/src/shared/backend/ext-api-conversion.tsx 100.00% <100.00%> (ø)
shared/src/api/client/services/hover.ts 92.85% <100.00%> (+1.19%) ⬆️
... and 5 more

@felixfbecker felixfbecker merged commit 89c6895 into master Apr 17, 2020
@felixfbecker felixfbecker deleted the provider-loading-states branch April 17, 2020 06:41
This was referenced Apr 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug An error, flaw or fault that produces an incorrect or unexpected result, or behavior. extensions Sourcegraph extensions
Projects
None yet
3 participants