Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/zero-rust-define.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'zero-rust': minor
---

Add typed named queries and mutators behind a new `serde` feature, shaped like the Swift client's `defineQuery` and `defineMutator`. `define::define_query(name, |args, ctx| query)` and `define::define_mutator(name, |tx, args, ctx| ...)` take serde argument types, and `.with(args)` makes a `QueryRequest` for `materialize`, `run`, and `preload` (built with the `Zero`'s context) or a `client::MutateRequest` for the new `Zero::mutate_request`. `json::Value` implements serde, with `json::to_value` and `json::from_value`. `materialize`, `run`, and `preload` now take any `query::ZeroQuery` (an `AnyQuery`, a typed `Query`, or a `QueryRequest`), so existing calls keep working; the default build still has no third-party dependencies.
2 changes: 1 addition & 1 deletion apps/zbugs-gpui/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion apps/zbugs-gpui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ tokio-tungstenite = { version = "0.30", default-features = false, features = [
ureq = { version = "3", default-features = false, features = ["rustls"] }
zbugs-model = { path = "../zbugs-model" }
# SQLite storage, so the app keeps its data across launches.
zero-client = { path = "../../rust/zero-client", features = ["sqlite"] }
zero-client = { path = "../../rust/zero-client", features = [
"sqlite",
"serde",
] }

[features]
# The end-to-end run CI does against zbugs' backend: see src/app/e2e.rs.
Expand Down
96 changes: 41 additions & 55 deletions apps/zbugs-gpui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,16 @@ use std::rc::Rc;
use zbugs_model::args::*;
use zbugs_model::mutators::{
ADD_COMMENT, ADD_EMOJI_TO_COMMENT, ADD_EMOJI_TO_ISSUE, ADD_LABEL, CREATE_AND_ADD_LABEL,
CREATE_ISSUE, DELETE_ISSUE, DELETE_ISSUES, EDIT_COMMENT, MutateRequest, REMOVE_COMMENT,
REMOVE_EMOJI, REMOVE_LABEL, SET_USER_PREF, SET_VIEW_STATE, UPDATE_ISSUE, UPDATE_NOTIFICATION,
CREATE_ISSUE, DELETE_ISSUE, DELETE_ISSUES, EDIT_COMMENT, REMOVE_COMMENT, REMOVE_EMOJI,
REMOVE_LABEL, SET_USER_PREF, SET_VIEW_STATE, UPDATE_ISSUE, UPDATE_NOTIFICATION,
};
use zbugs_model::queries::{
ALL_PROJECTS, COMMENTS_PAGE, ISSUE_DETAIL, ISSUE_LIST_COUNT, ISSUE_LIST_V2, LABELS, USER,
USER_PREF, USERS_FOR_PROJECT,
};
use zbugs_model::schema::{Comment, Emoji, Issue, IssueVisibility, Label, Project, User, UserPref};
use zbugs_model::{AuthData, ZERO_PROJECT_NAME, make_id};
use zero_client::client::{ConnectionState, MutationOutcome};
use zero_client::client::{ConnectionState, MutateRequest, MutationOutcome};
use zero_client::ttl::Ttl;

/// Rows the list asks for at a time, as zbugs' list pages.
Expand Down Expand Up @@ -468,16 +468,15 @@ impl ZbugsApp {
});

let (list, count) = Self::queries(&session, &filters, limit, cx);
let auth = session.auth.as_ref();
let projects = Live::new(&session.zero, ALL_PROJECTS.call(&(), auth), &ttl(), cx);
let projects = Live::new(&session.zero, ALL_PROJECTS.with(()), &ttl(), cx);
let (creators, assignees, crew, labels) =
Self::project_lives(&session, &filters.project, cx);
let me = session
.auth
.as_ref()
.map(|a| Live::new(&session.zero, USER.call(&a.sub, Some(a)), &ttl(), cx));
let skin_tone = session.auth.as_ref().map(|a| {
let q = USER_PREF.call(&SKIN_TONE_PREF.to_owned(), Some(a));
.map(|a| Live::new(&session.zero, USER.with(a.sub.clone()), &ttl(), cx));
let skin_tone = session.auth.as_ref().map(|_| {
let q = USER_PREF.with(SKIN_TONE_PREF.to_owned());
Live::new(&session.zero, q, &ttl(), cx)
});
// A zero-cache address the client can't take, from the environment
Expand Down Expand Up @@ -746,7 +745,7 @@ impl ZbugsApp {
label_name: name,
project_id: None,
};
self.mutate(CREATE_AND_ADD_LABEL.with(&args), cx);
self.mutate(CREATE_AND_ADD_LABEL.with(args), cx);
true
}

Expand Down Expand Up @@ -883,28 +882,22 @@ impl ZbugsApp {

/// The creators, assignees, crew, and labels of `project`.
fn project_lives(session: &Session, project: &str, cx: &mut Context<Self>) -> ProjectLives {
let auth = session.auth.as_ref();
let mut users = |filter| {
let args = UsersForProjectArgs {
disabled: false,
login: None,
project_name: project.into(),
filter: Some(filter),
};
Live::new(
&session.zero,
USERS_FOR_PROJECT.call(&args, auth),
&ttl(),
cx,
)
Live::new(&session.zero, USERS_FOR_PROJECT.with(args), &ttl(), cx)
};
let creators = users(UsersFilter::Creators);
let assignees = users(UsersFilter::Assignees);
let crew = users(UsersFilter::Crew);
let args = ProjectArgs {
project_name: project.into(),
};
let labels = Live::new(&session.zero, LABELS.call(&args, auth), &ttl(), cx);
let labels = Live::new(&session.zero, LABELS.with(args), &ttl(), cx);
(creators, assignees, crew, labels)
}

Expand Down Expand Up @@ -962,19 +955,15 @@ impl ZbugsApp {
limit: i64,
cx: &mut Context<Self>,
) -> (Live<Vec<Issue>>, Live<i64>) {
let auth = session.auth.as_ref();
let list_context = Self::list_context(filters);
let list = ISSUE_LIST_V2.call(
&IssueListV2Args {
list_context: list_context.clone(),
limit: Some(limit),
start: None,
dir: PageDirection::Forward,
inclusive: None,
},
auth,
);
let count = ISSUE_LIST_COUNT.call(&IssueListCountArgs { list_context }, auth);
let list = ISSUE_LIST_V2.with(IssueListV2Args {
list_context: list_context.clone(),
limit: Some(limit),
start: None,
dir: PageDirection::Forward,
inclusive: None,
});
let count = ISSUE_LIST_COUNT.with(IssueListCountArgs { list_context });
(
Live::new(&session.zero, list, &ttl(), cx),
Live::new(&session.zero, count, &ttl(), cx),
Expand Down Expand Up @@ -1034,13 +1023,10 @@ impl ZbugsApp {
if self.selected.as_deref() == Some(id.as_str()) {
return;
}
let q = ISSUE_DETAIL.call(
&IssueDetailArgs {
id_field: IdField::Id,
id: IssueKey::String(id.clone()),
},
self.session.auth.as_ref(),
);
let q = ISSUE_DETAIL.with(IssueDetailArgs {
id_field: IdField::Id,
id: IssueKey::String(id.clone()),
});
self.detail = Some(Live::new(&self.session.zero, q, &ttl(), cx));
self.selected = Some(id);
self.comments_limit = COMMENT_PAGE;
Expand Down Expand Up @@ -1105,15 +1091,15 @@ impl ZbugsApp {
dir: PageDirection::Backward,
inclusive: false,
};
let q = COMMENTS_PAGE.call(&args, self.session.auth.as_ref());
let q = COMMENTS_PAGE.with(args);
self.comment_page = Some(Live::new(&self.session.zero, q, &ttl(), cx));
cx.notify();
}

/// Runs a mutation and shows why it failed, on the client or the
/// server.
fn mutate(&mut self, request: MutateRequest, cx: &mut Context<Self>) {
let result = request.run(&self.session.zero);
let result = self.session.zero.mutate_request(request);
if let Some(Ok(MutationOutcome::AppError { message, .. })) = result.client.result() {
self.error = Some(message);
cx.notify();
Expand Down Expand Up @@ -1203,12 +1189,12 @@ impl ZbugsApp {
cx.notify();
return false;
}
self.mutate(EDIT_COMMENT.with(&EditCommentArgs { id, body }), cx);
self.mutate(EDIT_COMMENT.with(EditCommentArgs { id, body }), cx);
true
}

fn delete_comment(&mut self, id: &String, cx: &mut Context<Self>) {
self.mutate(REMOVE_COMMENT.with(id), cx);
self.mutate(REMOVE_COMMENT.with(id.to_owned()), cx);
}

/// Asks before a delete, which `delete` does.
Expand Down Expand Up @@ -1252,7 +1238,7 @@ impl ZbugsApp {
/// Deletes issue `id`, and leaves it if it's selected, as the web app
/// goes back to the list.
fn delete_issue(&mut self, id: &String, cx: &mut Context<Self>) {
self.mutate(DELETE_ISSUE.with(id), cx);
self.mutate(DELETE_ISSUE.with(id.to_owned()), cx);
self.leave_deleted(std::slice::from_ref(id), cx);
}

Expand Down Expand Up @@ -1296,7 +1282,7 @@ impl ZbugsApp {
return;
};
let ids = checked.into_iter().collect::<Vec<_>>();
self.mutate(DELETE_ISSUES.with(&ids), cx);
self.mutate(DELETE_ISSUES.with(ids.clone()), cx);
self.leave_deleted(&ids, cx);
}

Expand All @@ -1322,7 +1308,7 @@ impl ZbugsApp {
}),
..UpdateIssueArgs::new(issue_id, now_ms())
};
self.mutate(UPDATE_ISSUE.with(&args), cx);
self.mutate(UPDATE_ISSUE.with(args), cx);
}

fn set_subscribed(&mut self, issue_id: String, subscribe: bool, cx: &mut Context<Self>) {
Expand All @@ -1335,7 +1321,7 @@ impl ZbugsApp {
},
created: now_ms(),
};
self.mutate(UPDATE_NOTIFICATION.with(&args), cx);
self.mutate(UPDATE_NOTIFICATION.with(args), cx);
}

/// Tells the user when someone else comments on or reacts to the
Expand Down Expand Up @@ -1466,7 +1452,7 @@ impl ZbugsApp {
issue_id: id,
viewed: now_ms().max(modified),
};
this.mutate(SET_VIEW_STATE.with(&args), cx);
this.mutate(SET_VIEW_STATE.with(args), cx);
}
this.mark_viewed(cx);
});
Expand All @@ -1479,25 +1465,25 @@ impl ZbugsApp {
open: Some(!issue.open),
..UpdateIssueArgs::new(issue.id.clone(), now_ms())
};
self.mutate(UPDATE_ISSUE.with(&args), cx);
self.mutate(UPDATE_ISSUE.with(args), cx);
}

fn set_assignee(&mut self, issue_id: String, user_id: Option<String>, cx: &mut Context<Self>) {
let args = UpdateIssueArgs {
assignee_id: Some(user_id),
..UpdateIssueArgs::new(issue_id, now_ms())
};
self.mutate(UPDATE_ISSUE.with(&args), cx);
self.mutate(UPDATE_ISSUE.with(args), cx);
}

/// Adds the label to the issue, or takes it off when it has it.
fn toggle_label(&mut self, issue: &Issue, label_id: String, cx: &mut Context<Self>) {
let has = issue.labels.iter().flatten().any(|l| l.id == label_id);
let issue_id = issue.id.clone();
let request = if has {
REMOVE_LABEL.with(&RemoveLabelArgs { issue_id, label_id })
REMOVE_LABEL.with(RemoveLabelArgs { issue_id, label_id })
} else {
ADD_LABEL.with(&IssueLabelArgs {
ADD_LABEL.with(IssueLabelArgs {
issue_id,
label_id,
project_id: None,
Expand All @@ -1522,7 +1508,7 @@ impl ZbugsApp {
key: SKIN_TONE_PREF.to_owned(),
value: tone.to_string(),
};
self.mutate(SET_USER_PREF.with(&args), cx);
self.mutate(SET_USER_PREF.with(args), cx);
}

/// Takes back the user's reaction with `unicode`, or adds it.
Expand All @@ -1543,7 +1529,7 @@ impl ZbugsApp {
.find(|e| e.creator_id == me && normalize_emoji(&e.value) == key)
{
let id = mine.id.clone();
self.mutate(REMOVE_EMOJI.with(&id), cx);
self.mutate(REMOVE_EMOJI.with(id), cx);
return;
}
let annotation = REACTIONS
Expand All @@ -1569,7 +1555,7 @@ impl ZbugsApp {
Subject::Issue => ADD_EMOJI_TO_ISSUE,
Subject::Comment => ADD_EMOJI_TO_COMMENT,
};
self.mutate(add.with(&args), cx);
self.mutate(add.with(args), cx);
}

/// Opens the picker with every emoji, to react with any of them.
Expand Down Expand Up @@ -1623,7 +1609,7 @@ impl ZbugsApp {
body,
created: now_ms(),
};
self.mutate(ADD_COMMENT.with(&args), cx);
self.mutate(ADD_COMMENT.with(args), cx);
self.comment
.update(cx, |state, cx| state.set_value("", window, cx));
}
Expand Down Expand Up @@ -1735,7 +1721,7 @@ impl ZbugsApp {
description: Some(description),
..UpdateIssueArgs::new(id, now)
};
self.mutate(UPDATE_ISSUE.with(&args), cx);
self.mutate(UPDATE_ISSUE.with(args), cx);
}
None => {
let Some(project_id) = self.project_id() else {
Expand All @@ -1755,7 +1741,7 @@ impl ZbugsApp {
modified: now,
project_id,
};
self.mutate(CREATE_ISSUE.with(&args), cx);
self.mutate(CREATE_ISSUE.with(args), cx);
self.select(id, cx);
}
}
Expand Down
7 changes: 4 additions & 3 deletions apps/zbugs-gpui/src/live.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ use std::cell::RefCell;
use std::rc::Rc;
use zbugs_model::Context as Auth;
use zero_client::client::{MaterializedView, Zero};
use zero_client::define::QueryRequest;
use zero_client::ivm::ResultType;
use zero_client::ttl::Ttl;
use zero_client::typed::{Query, QueryValue};
use zero_client::typed::QueryValue;

pub struct Live<T> {
value: Rc<RefCell<Snapshot<T>>>,
Expand All @@ -34,7 +35,7 @@ impl<T: QueryValue + 'static> Live<T> {
/// rows change, until dropped.
pub fn new<R: 'static, V: 'static>(
zero: &Zero<Auth>,
query: Query<R, T>,
query: QueryRequest<Auth, R, T>,
ttl: &Ttl,
cx: &mut Context<V>,
) -> Live<T> {
Expand All @@ -45,7 +46,7 @@ impl<T: QueryValue + 'static> Live<T> {
}));
let (wake, mut woken) = mpsc::unbounded::<()>();
let view = zero
.materialize(query.any(), ttl)
.materialize(&query, ttl)
.expect("a query of the app's schema materializes");
let v = value.clone();
let remove = view.add_listener(move |data, result_type, _| {
Expand Down
9 changes: 4 additions & 5 deletions apps/zbugs-gpui/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,11 @@ impl Session {
let project = ProjectArgs {
project_name: ZERO_PROJECT_NAME.into(),
};
let a = auth.as_ref();
let preloads = vec![
zero.preload(ISSUE_PRELOAD_V2.call(&project, a).any(), &ttl),
zero.preload(ALL_USERS.call(&(), a).any(), &ttl),
zero.preload(ALL_LABELS.call(&(), a).any(), &ttl),
zero.preload(ALL_PROJECTS.call(&(), a).any(), &ttl),
zero.preload(&ISSUE_PRELOAD_V2.with(project), &ttl),
zero.preload(&ALL_USERS.with(()), &ttl),
zero.preload(&ALL_LABELS.with(()), &ttl),
zero.preload(&ALL_PROJECTS.with(()), &ttl),
];
Session {
zero: Rc::new(zero),
Expand Down
Loading
Loading