-
Notifications
You must be signed in to change notification settings - Fork 309
Add and handle live-updates to saved snippets data #1511
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
base: main
Are you sure you want to change the base?
Conversation
c27690a
to
c5cc482
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!! Small comments below.
@@ -37,6 +37,13 @@ sealed class Event { | |||
case 'update': return RealmUserUpdateEvent.fromJson(json); | |||
default: return UnexpectedEvent.fromJson(json); | |||
} | |||
case 'saved_snippets': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
api: Add save_snippets events and handle live-updates
Commit-message nit: saved_snippets
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also this is labeled as an "api:" commit, but it also adds to model code; how about separate api:
and model:
commits?
(Or perhaps just one model:
commit with the API-binding additions lumped into that same commit—but if doing that, it would be logical to lump all the new API-binding code into that commit, including the initial-snapshot code.)
lib/model/saved_snippet.dart
Outdated
Map<int, SavedSnippet> get savedSnippets; | ||
} | ||
|
||
class SavedSnippetStoreImpl with SavedSnippetStore { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally a substore would extend PerAccountStoreBase
…it seems we don't depend on that here though 🙂 but maybe it's helpful to do it anyway, in case it's needed in the future, and also as an example for future substore implementations?
diff --git lib/model/saved_snippet.dart lib/model/saved_snippet.dart
index 66b50781d..92cb253ef 100644
--- lib/model/saved_snippet.dart
+++ lib/model/saved_snippet.dart
@@ -2,14 +2,18 @@ import 'package:collection/collection.dart';
import '../api/model/events.dart';
import '../api/model/model.dart';
+import 'store.dart';
mixin SavedSnippetStore {
Map<int, SavedSnippet> get savedSnippets;
}
-class SavedSnippetStoreImpl with SavedSnippetStore {
- SavedSnippetStoreImpl({required Iterable<SavedSnippet> savedSnippets})
- : _savedSnippets = {
+class SavedSnippetStoreImpl extends PerAccountStoreBase with SavedSnippetStore {
+ SavedSnippetStoreImpl({
+ required super.core,
+ required Iterable<SavedSnippet> savedSnippets,
+ }) :
+ _savedSnippets = {
for (final savedSnippet in savedSnippets)
savedSnippet.id: savedSnippet,
};
test/model/saved_snippet.dart
Outdated
savedSnippets: [eg.savedSnippet(id: 101)])); | ||
check(store).savedSnippets.values.single.id.equals(101); | ||
|
||
await store.handleEvent(SavedSnippetsAddEvent(id: 1,savedSnippet: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: space after comma; actually maybe put savedSnippet:
on the next line, with eg.savedSnippet(
?
Thanks! Updated the PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Small comments below.
part 'saved_snippets.g.dart'; | ||
|
||
/// https://zulip.com/api/create-saved-snippet | ||
Future<CreateSavedSnippetResult> createSavedSnippet(ApiConnection connection, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
api: Add createSavedSnippet route
This needs a simple smoke test in test/api/route/.
case SavedSnippetsEvent(): | ||
assert(debugLog('server event: saved_snippets/${event.op}')); | ||
_savedSnippets.handleSavedSnippetsEvent(event); | ||
notifyListeners(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I guess this case needs to be handled (as a no-op) in the earlier commit—
api: Add saved_snippets events
—to satisfy the analyzer there.
Map<int, SavedSnippet> get savedSnippets; | ||
} | ||
|
||
class SavedSnippetStoreImpl extends PerAccountStoreBase with SavedSnippetStore { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
model: Handle live-updates to savedSnippets
Commit-message nit: this commit does more than just handle live-updates to savedSnippets—it's what adds savedSnippets into the codebase in the first place :)
Could be:
model: Add PerAccountStore.savedSnippets, updating with events
or similar.
Thanks! Pushed an update. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! One nit below, and I'll mark for Greg's review.
check(connection.takeRequests()).single.isA<http.Request>() | ||
.bodyFields.deepEquals({ | ||
'title': 'test saved snippet', | ||
'content': 'content', | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check(connection.takeRequests()).single.isA<http.Request>() | |
.bodyFields.deepEquals({ | |
'title': 'test saved snippet', | |
'content': 'content', | |
}); | |
check(connection.takeRequests()).single.isA<http.Request>() | |
.bodyFields.deepEquals({ | |
'title': 'test saved snippet', | |
'content': 'content', | |
}); |
(I think)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh right. Oops 😬
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks to you both! I've done a quick skim, and this structure looks good. I like the organization with the separate API and model/store commits (as per #1511 (comment)), with this to decouple the API event commit from the store changes:
case SavedSnippetsEvent():
// TODO handle
break;
I'll aim to do a full review tomorrow.
lib/api/model/events.dart
Outdated
/// The corresponding API docs are in several places for | ||
/// different values of `op`; see subclasses. | ||
sealed class SavedSnippetsEvent extends Event { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: It looks like these docs are actually next to each other, so this class can usefully link to the one that comes first. (The subclasses can still link to the individual sections.)
This wording was originally for realm_user
, which is actually scattered across two widely-separated places in https://zulip.com/api/get-events . (And I see I wrote it at the same time for stream
but it's not true of stream
now; not sure if that was an oversight or that one got fixed since then.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, and here's a full review — just small comments below.
(And I see you've already revised to handle my one comment above, thanks.)
lib/model/store.dart
Outdated
@override | ||
Map<int, SavedSnippet> get savedSnippets => _savedSnippets.savedSnippets; | ||
final SavedSnippetStoreImpl _savedSnippets; | ||
|
||
final UserSettings? userSettings; // TODO(server-5) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: let's put the new members after userSettings — the user's overall settings are a more fundamental piece of data than the saved snippets specifically (which are basically a particular setting)
test/api/model/model_checks.dart
Outdated
extension SavedSnippetChecks on Subject<SavedSnippet> { | ||
Subject<int> get id => has((x) => x.id, 'id'); | ||
Subject<String> get title => has((x) => x.title, 'title'); | ||
Subject<String> get content => has((x) => x.content, 'content'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: let's include the remaining field too, even if tests don't currently use it — completeness saves the need to go back and add it later
BTW in general I find it efficient to write these in batch: first get to a set of lines like
int id;
String title;
String content;
int dateCreated;
Then go into multi-cursor mode, with a cursor at the start of each of those lines: for example, in Android Studio on Linux, I double-tap Ctrl, hold the second tap, then hit Down a few times to make more cursors.
Then Ctrl+Left / Ctrl+Right to move by a word at a time; Ctrl+Shift+Right to do so while selecting, to select the name; the usual Ctrl+C for copy will copy the set of different names, and Ctrl+V will paste the different names on their respective lines. So the rest of the line, like Subject<
, > get
, => has((x) => x.
, , '
, ')
, I type just once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's neat! I usually went line by line replacing the symbols and the string. This sounds more efficient when you have multiple fields.
test/model/saved_snippet.dart
Outdated
import '../example_data.dart' as eg; | ||
import 'store_checks.dart'; | ||
|
||
void main() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
filename should end with _test.dart
Otherwise I believe flutter test
won't include it. (Though even if it did, following a uniform pattern is helpful for us human readers.)
lib/api/route/saved_snippets.dart
Outdated
}) { | ||
return connection.post('createSavedSnippet', CreateSavedSnippetResult.fromJson, 'saved_snippets', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
}) { | |
return connection.post('createSavedSnippet', CreateSavedSnippetResult.fromJson, 'saved_snippets', { | |
}) { | |
assert(connection.zulipFeatureLevel! >= 297); // TODO(server-10) | |
return connection.post('createSavedSnippet', CreateSavedSnippetResult.fromJson, 'saved_snippets', { |
That doubles as a reminder, when writing code that calls this endpoint, of what feature level to check for before offering the feature in the UI.
return FakeApiConnection.with_((connection) async { | ||
connection.prepare( | ||
json: CreateSavedSnippetResult(savedSnippetId: 123).toJson()); | ||
final result = await createSavedSnippet(connection, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this file goes in test/api/route/ :slightly_smiling_face: (not …/model/)
check(connection.takeRequests()).single.isA<http.Request>() | ||
.bodyFields.deepEquals({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As long as we're writing this test, it's only a couple more lines to have it check the other key details of the request too: the method and URL path. So let's include those too.
check(result).savedSnippetId.equals(123); | ||
check(connection.takeRequests()).single.isA<http.Request>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: the order is a bit more logical if we check the request first, then the response — after all, the request had to happen before we would receive the response
Thanks! Updated the PR. |
This prepares the data model and API bindings for #1391 towards #863.