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

Update review list right after user posting/revoking #670

Closed
wants to merge 46 commits into from

Conversation

yiningwang11
Copy link
Contributor

fixes #613
The review list is not updated because it always gets loaded from the disk cache. Making the 'getReviews' always get data from the registry instead of from the cache fixes this issue.

@amvanbaren
Copy link
Contributor

Thanks for your contribution @yiningwang11. It works as expected. I think it would be better to use the ETag header instead of fixed duration caching or no caching at all.

It's a combination of creating an ETag of the JSON response:

protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
// limit the filter to /api/{namespace}/{extension}, /api/{namespace}/details
// and /api/{namespace}/{extension}/{version} endpoints
var path = request.getRequestURI().substring(1).split("/");
var applyFilter = path.length == 3 || path.length == 4;
if(applyFilter) {
applyFilter = path[0].equals("api") && !path[1].equals("-");
}
if(applyFilter && path.length == 4) {
applyFilter = !(path[3].equals("review") || path[3].equals("reviews"));
}
return !applyFilter;
}

caching the response:

@Override
@Cacheable(value = CACHE_EXTENSION_JSON, keyGenerator = GENERATOR_EXTENSION_JSON)
public ExtensionJson getExtension(String namespace, String extensionName, String targetPlatform, String version) {
var extVersion = findExtensionVersion(namespace, extensionName, targetPlatform, version);
var json = toExtensionVersionJson(extVersion, targetPlatform, true, false);
json.downloads = getDownloads(extVersion.getExtension(), targetPlatform, extVersion.getVersion());
return json;
}

and evicting the cache at the right time:

@yiningwang11
Copy link
Contributor Author

Thank you for your suggestion. I'll continue look into it :)

Copy link
Member

@filiptronicek filiptronicek left a comment

Choose a reason for hiding this comment

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

Awesome, the reviews seem to be reflected instantly!

I think this issue is related -- the star icons don't seem to reflect the newly posted review.
image

@yiningwang11
Copy link
Contributor Author

Awesome, the reviews seem to be reflected instantly!

I think this issue is related -- the star icons don't seem to reflect the newly posted review. image

I'll take a look on this. Thanks for your review!

Copy link
Member

@filiptronicek filiptronicek left a comment

Choose a reason for hiding this comment

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

Nice! The extension ⭐ rating gets updated now as well. The only thing missing for me is the ⭐ rating update on the homepage. Could you please take a look? 🙏

image

(the highlighted extension I just reviewed)

@@ -750,7 +750,7 @@ public ResponseEntity<SearchResultJson> search(
}

return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES).cachePublic())
.cacheControl(CacheControl.noCache().cachePublic())
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if there is a better way of replacing the cache instead of disabling it altogether? cc @amvanbaren

Otherwise I'm good with the changes

Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if there is a better way of replacing the cache instead of disabling it altogether?

Yes, a combination of server caching and generating an ETag for the response. If the request contains the If-None-Match header and the value matches the ETag value, then no content (204) is returned because the client already has the most up to date response. #670 (comment)

@@ -16,16 +16,12 @@ public class ShallowEtagHeaderFilter extends org.springframework.web.filter.Shal

protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
// limit the filter to /api/{namespace}/{extension}, /api/{namespace}/details
// and /api/{namespace}/{extension}/{version} endpoints
// and /api/{namespace}/{extension}/{version}, and /api/-/search endpoints
Copy link
Contributor

Choose a reason for hiding this comment

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

@yiningwang11 Why generate an ETag for the /api/-/search endpoint?

Copy link
Contributor Author

@yiningwang11 yiningwang11 Mar 24, 2023

Choose a reason for hiding this comment

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

Hi, @amvanbaren thanks for the review!
I added this to generate an ETag for the response because I have disabled the cache for the search endpoint that returns the homepage. I thought with an ETag coming with the response header, even though the cache is disabled, no content would still be returned if the client has the up-to-date response.

Copy link
Contributor

Choose a reason for hiding this comment

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

@yiningwang11 Ok, that's true. The downside is that the search is fully processed. Only at the end the ShallowEtagHeaderFilter compares the If-None-Match request header with the hash of the JSON body and discards the body if the two Etags match.

On the other hand #542 caches search results on the server side, so this approach will work. I'll test #542 and merge it in.

@yiningwang11 yiningwang11 requested review from filiptronicek and amvanbaren and removed request for filiptronicek March 28, 2023 17:33
@@ -111,7 +111,9 @@ public ExtensionJson getExtension(String namespace, String extensionName, String
public ExtensionJson getExtension(String namespace, String extensionName, String targetPlatform, String version) {
var extVersion = findExtensionVersion(namespace, extensionName, targetPlatform, version);
var json = toExtensionVersionJson(extVersion, targetPlatform, true, false);
var extension = repositories.findExtension(extensionName, namespace);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove this statement, because you don't have to set averageRating here.

json.downloads = getDownloads(extVersion.getExtension(), targetPlatform, extVersion.getVersion());
json.averageRating = extension.getAverageRating();
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove this statement, because averageRating is already set in ExtensionVersion.toExtensionJson.

Copy link
Contributor Author

@yiningwang11 yiningwang11 Mar 30, 2023

Choose a reason for hiding this comment

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

Hello @amvanbaren , thanks for your comment!
The reason that I set averageRating here is because I found out in ExtensionVersion.toExtensionJson, the averageRating is not updated immediately according to deleting/posting new reviews with new ratings of an extension. However, the extension.averageRating is being updated correctly in time. That's why I put an extra statement here to update the averageRating.

Copy link
Contributor

@amvanbaren amvanbaren Mar 31, 2023

Choose a reason for hiding this comment

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

Ok, I see what you mean. I went through the code to find out how this could happen. The VersionService caches the latest ExtensionVersion. When a review is posted or deleted your fork doesn't evict the latest version cache, which causes findExtensionVersion to return a cached instance of the lastest ExtensionVersion with an old Extension instance (averageRating not updated).

This has been fixed in master. Can you sync your master branch and rebase the updateReview branch? Then the extra database call shouldn't be necessary anymore.

You can sync the master branch by clicking Sync fork in the right corner of your Github repo:
Schermafbeelding 2023-03-31 om 11 46 01

After syncing your fork you can rebase by opening a terminal in your openvsx directory and following these steps:

git checkout master
git pull
git checkout updateReview
git rebase master
<FIX CONFLICTS & COMMIT>
git rebase --continue
git push -f origin updateReview

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hello @amvanbaren , thanks for your suggestion!
I've followed these commands but seems like git push -f origin updateReview has caused one of the checks to be failing. It looks like one of the authors of the commits is not covered by the ECA. I'm not sure if that's gonna affect anything, or what should I do to fix this?
Thank you again for your help :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Hi @yiningwang11, it looks like you merged openvsx:master on top of your updateReview branch. yiningwang11:master is still 70 commits behind eclipse:master.
yiningwang11-master

A rebase changes the base of the branch, but the changes you've made stay on top. This article explains it in further detail: https://www.atlassian.com/git/tutorials/rewriting-history/git-rebase. The example under the Usage heading describes what we try to do here.

Are the top 5 commits for this feature or were there more commits?
yiningwang11-commits

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hello @amvanbaren , thanks for your comment! I went over the article you recommended. Is this because I should have sync my yiningwang11:master branch and then rebase the updateReview feature branch, instead of merge the openvsx:master on top of my updateReview feature branch?
Is there anything I can do to make things right at the moment?

The top 5 commits in the screenshot you shared were for this feature, there aren't any more commits yet. (I'm still working on adding the @Cacheable annotation on LocalRegistryService.getReviews now)

Thank you :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this because I should have sync my yiningwang11:master branch and then rebase the updateReview feature branch, instead of merge the openvsx:master on top of my updateReview feature branch?

Yes, it is so that this feature is current with master. This ensures that you don't run into issues that already have been fixed in master and that there are no conflicts when this branch is merged into master.

Is there anything I can do to make things right at the moment?

Yes, you can use git rebase to drop the unwanted commits.
First, do git stash to store your current changes.
Then set VS Code as your default git editor. You can skip this step if you're familiar with vim.

git config core.editor "code --wait"

Next, do an interactive rebase on your updateReview branch. I see this PR has 46 commits. If you have more commits locally then increase the number in the below command, e.g. you've added 2 more commits, then the number becomes 48.

git rebase -i HEAD~46

You get a list of commits. You need to keep the first 5 commits and if you've added extra commits since this PR you want to keep those too. All the other commits in between you want to drop.
rebase-drop

When you're 100% sure (you can't undo this operation) which commits to keep and which to drop, close the editor. The rebase is applied and the commits from the merge with master should be gone.

Force push updateReview

git push -f origin updateReview

If you used git stash, you can use git stash apply to get your local working changes back.

@@ -777,6 +779,7 @@ private List<SearchEntryJson> toSearchEntries(SearchHits<ExtensionSearch> search
.map(e -> {
var entry = e.getValue().toSearchEntryJson();
entry.url = createApiUrl(serverUrl, "api", entry.namespace, entry.name);
entry.averageRating = repositories.findExtension(entry.name, entry.namespace).getAverageRating();
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove this statement, because averageRating is already set in ExtensionVersion.toSearchEntryJson.

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 added this statement due to the same reason. I found ExtensionVersion.toSearchEntryJson is not updating averageRating attribute according to deleting/posting reviews with new ratings.

@@ -16,16 +16,12 @@ public class ShallowEtagHeaderFilter extends org.springframework.web.filter.Shal

protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
// limit the filter to /api/{namespace}/{extension}, /api/{namespace}/details
// and /api/{namespace}/{extension}/{version} endpoints
// and /api/{namespace}/{extension}/{version}, and /api/-/search endpoints
Copy link
Contributor

Choose a reason for hiding this comment

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

@yiningwang11 Ok, that's true. The downside is that the search is fully processed. Only at the end the ShallowEtagHeaderFilter compares the If-None-Match request header with the hash of the JSON body and discards the body if the two Etags match.

On the other hand #542 caches search results on the server side, so this approach will work. I'll test #542 and merge it in.

@@ -632,7 +632,7 @@ public ResponseEntity<ReviewListJson> getReviews(
for (var registry : getRegistries()) {
try {
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES).cachePublic())
.cacheControl(CacheControl.noCache().cachePublic())
Copy link
Contributor

Choose a reason for hiding this comment

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

@yiningwang11 Can you also make LocalRegistryService.getReviews @Cacheable? Please make sure to evict entries from the cache when a review is added (LocalRegistryService.postReview) or deleted (LocalRegistryService.deleteReview) and when UserData changes in UserService.updateExistingUser

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@amvanbaren I'll take a look into this. Thank you!

}
if(applyFilter && path.length == 4) {
applyFilter = !(path[3].equals("review") || path[3].equals("reviews"));
Copy link
Contributor

Choose a reason for hiding this comment

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

Please keep applyFilter = !(path[3].equals("review");, because I think we don't want to use ETags when a review is added or deleted.

yiningwang11 and others added 15 commits April 1, 2023 20:54
rebase branch "updateReview" onto branch "master"
* Access token dialog improvements

* Set focus on the Copy button

---------

Co-authored-by: amvanbaren <[email protected]>
* Search bar improvements

* Remove 'Space' key in extension-list-header.tsx
Change charCode 13 to key 'Enter' in namespace-input.tsx

---------

Co-authored-by: amvanbaren <[email protected]>
Fixes eclipse#427

Change Namespace
Evict caches
Update search entries
Fixes eclipse#452
Added UpstreamProxyService
Add url rewriting to UpstreamVSCodeService.java

# Conflicts:
#	server/src/main/java/org/eclipse/openvsx/UpstreamRegistryService.java
#	server/src/main/java/org/eclipse/openvsx/adapter/UpstreamVSCodeService.java
Update extension publicId of already published extensions when a new extension has the same publicId
Use react-helmet to make <head> tags configurable through pageSettings for homepage, extension and namespace pages
Simplify header tags
Pass params instead of only name
bump webui version

# Conflicts:
#	webui/package.json
amvanbaren and others added 26 commits April 1, 2023 21:01
Add possibility to delay running migrations, so that deployment can finish before migrations run
Added FixTargetPlatformMigration
Remove extensionFile at end of a migration
Don't run migrations in mirror mode
Improve social links UX
Use timestamp in namespace logo to bust browser cache
Currently copy-pasting some commands with smart quotes results in errors.
Replace all the smart quotes with straight quotes for consistency.

Signed-off-by: David Xia <[email protected]>
server: move files to new namespace in background job
webui: show confirmation dialog, instead of loading new namespace
Bumps [webpack](https://github.com/webpack/webpack) from 5.75.0 to 5.76.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](webpack/webpack@v5.75.0...v5.76.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <[email protected]>
Merge namespace members
Return error if namespaces contain duplicate extensions
@amvanbaren
Copy link
Contributor

@yiningwang11 I've opened PR #761. It contains your changes and clears the cache when a review is posted or deleted.

@amvanbaren amvanbaren closed this Jun 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Reviews don't appear right away leading to a confusing user flow
5 participants