Skip to content

Commit

Permalink
Support getting batches of summary updates via LocalUserIndex (#4983)
Browse files Browse the repository at this point in the history
  • Loading branch information
hpeebles authored Dec 11, 2023
1 parent b174fcf commit e9c7e52
Show file tree
Hide file tree
Showing 21 changed files with 362 additions and 59 deletions.
4 changes: 4 additions & 0 deletions backend/canisters/community/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [unreleased]

### Added

- Support getting batches of summary updates via LocalUserIndex ([#4983](https://github.com/open-chat-labs/open-chat/pull/4983))

### Removed

- Removed `local_user_index` endpoint since that is now included in the summary ([#4977](https://github.com/open-chat-labs/open-chat/pull/4977))
Expand Down
10 changes: 9 additions & 1 deletion backend/canisters/community/api/src/queries/c2c_summary.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
pub type Args = super::summary::Args;
use candid::Principal;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub struct Args {
pub invite_code: Option<u64>,
pub on_behalf_of: Option<Principal>,
}

pub type Response = super::summary::Response;
12 changes: 12 additions & 0 deletions backend/canisters/community/api/src/queries/c2c_summary_updates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use candid::Principal;
use serde::{Deserialize, Serialize};
use types::TimestampMillis;

#[derive(Serialize, Deserialize, Debug)]
pub struct Args {
pub on_behalf_of: Option<Principal>,
pub updates_since: TimestampMillis,
pub invite_code: Option<u64>,
}

pub type Response = super::summary_updates::Response;
1 change: 1 addition & 0 deletions backend/canisters/community/api/src/queries/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod c2c_events;
pub mod c2c_events_by_index;
pub mod c2c_events_window;
pub mod c2c_summary;
pub mod c2c_summary_updates;
pub mod channel_summary;
pub mod channel_summary_updates;
pub mod deleted_message;
Expand Down
2 changes: 2 additions & 0 deletions backend/canisters/community/c2c_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use community_canister::*;
generate_c2c_call!(c2c_events);
generate_c2c_call!(c2c_events_by_index);
generate_c2c_call!(c2c_events_window);
generate_c2c_call!(c2c_summary);
generate_c2c_call!(c2c_summary_updates);

// Updates
generate_c2c_call!(c2c_create_proposals_channel);
Expand Down
19 changes: 13 additions & 6 deletions backend/canisters/community/impl/src/queries/summary.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
use crate::read_state;
use crate::RuntimeState;
use candid::Principal;
use canister_api_macros::query_msgpack;
use community_canister::c2c_summary::{Args as C2CArgs, Response as C2CResponse};
use community_canister::summary::{Response::*, *};
use ic_cdk_macros::query;

#[query]
fn summary(args: Args) -> Response {
read_state(|state| summary_impl(args, state))
read_state(|state| summary_impl(args.invite_code, None, state))
}

#[query_msgpack]
fn c2c_summary(args: Args) -> Response {
read_state(|state| summary_impl(args, state))
fn c2c_summary(args: C2CArgs) -> C2CResponse {
read_state(|state| summary_impl(args.invite_code, args.on_behalf_of, state))
}

fn summary_impl(args: Args, state: &RuntimeState) -> Response {
let caller = state.env.caller();
fn summary_impl(invite_code: Option<u64>, on_behalf_of: Option<Principal>, state: &RuntimeState) -> Response {
let caller = if let Some(principal) = on_behalf_of {
assert!(state.is_caller_local_user_index());
principal
} else {
state.env.caller()
};

if !state.data.is_accessible(caller, args.invite_code) {
if !state.data.is_accessible(caller, invite_code) {
return PrivateCommunity;
}

Expand Down
52 changes: 32 additions & 20 deletions backend/canisters/community/impl/src/queries/summary_updates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use crate::model::events::CommunityEventInternal;
use crate::model::members::CommunityMemberInternal;
use crate::RuntimeState;
use crate::{read_state, Data};
use candid::Principal;
use canister_api_macros::query_msgpack;
use community_canister::c2c_summary_updates::{Args as C2CArgs, Response as C2CResponse};
use community_canister::summary_updates::{Response::*, *};
use ic_cdk_macros::query;
use types::{
Expand All @@ -13,18 +15,28 @@ use types::{

#[query]
fn summary_updates(args: Args) -> Response {
read_state(|state| summary_updates_impl(args, state))
read_state(|state| summary_updates_impl(args.updates_since, args.invite_code, None, state))
}

#[query_msgpack]
fn c2c_summary_updates(args: Args) -> Response {
read_state(|state| summary_updates_impl(args, state))
fn c2c_summary_updates(args: C2CArgs) -> C2CResponse {
read_state(|state| summary_updates_impl(args.updates_since, args.invite_code, args.on_behalf_of, state))
}

fn summary_updates_impl(args: Args, state: &RuntimeState) -> Response {
let caller = state.env.caller();
fn summary_updates_impl(
updates_since: TimestampMillis,
invite_code: Option<u64>,
on_behalf_of: Option<Principal>,
state: &RuntimeState,
) -> Response {
let caller = if let Some(principal) = on_behalf_of {
assert!(state.is_caller_local_user_index());
principal
} else {
state.env.caller()
};

if !state.data.is_accessible(caller, args.invite_code) {
if !state.data.is_accessible(caller, invite_code) {
return PrivateCommunity;
}

Expand All @@ -36,10 +48,10 @@ fn summary_updates_impl(args: Args, state: &RuntimeState) -> Response {
.channels
.iter()
.filter_map(|c| state.data.channels.get(c))
.filter(|c| c.last_updated(Some(m.user_id)) > args.updates_since)
.filter(|c| c.last_updated(Some(m.user_id)) > updates_since)
.collect();

let channels_removed = m.channels_removed_since(args.updates_since);
let channels_removed = m.channels_removed_since(updates_since);

(channels_with_updates, channels_removed)
} else {
Expand All @@ -48,22 +60,22 @@ fn summary_updates_impl(args: Args, state: &RuntimeState) -> Response {
.channels
.public_channels()
.into_iter()
.filter(|c| c.last_updated(None) > args.updates_since)
.filter(|c| c.last_updated(None) > updates_since)
.collect();

(channels_with_updates, Vec::new())
};

if channels_with_updates.is_empty()
&& channels_removed.is_empty()
&& state.data.events.latest_event_timestamp() <= args.updates_since
&& state.data.members.user_groups_last_updated() <= args.updates_since
&& member_last_updated <= args.updates_since
&& state.data.events.latest_event_timestamp() <= updates_since
&& state.data.members.user_groups_last_updated() <= updates_since
&& member_last_updated <= updates_since
{
return SuccessNoUpdates;
}

let updates_from_events = process_events(args.updates_since, member, &state.data);
let updates_from_events = process_events(updates_since, member, &state.data);

let mut channels_added = Vec::new();
let mut channels_updated = Vec::new();
Expand All @@ -72,14 +84,14 @@ fn summary_updates_impl(args: Args, state: &RuntimeState) -> Response {
let is_community_member = member.is_some();

for channel in channels_with_updates {
if channel.date_imported.map_or(false, |ts| ts > args.updates_since) {
if channel.date_imported.map_or(false, |ts| ts > updates_since) {
if let Some(summary) = channel.summary(user_id, is_community_member, state.data.is_public, &state.data.members) {
channels_added.push(summary);
}
} else {
match channel.summary_updates(
user_id,
args.updates_since,
updates_since,
is_community_member,
state.data.is_public,
&state.data.members,
Expand All @@ -95,11 +107,11 @@ fn summary_updates_impl(args: Args, state: &RuntimeState) -> Response {
rules_accepted: m
.rules_accepted
.as_ref()
.filter(|accepted| updates_from_events.rules_changed || accepted.timestamp > args.updates_since)
.filter(|accepted| updates_from_events.rules_changed || accepted.timestamp > updates_since)
.map(|accepted| accepted.value >= state.data.rules.text.version),
display_name: m
.display_name()
.if_set_after(args.updates_since)
.if_set_after(updates_since)
.map_or(OptionUpdate::NoChange, |display_name| match display_name {
Some(display_name) => OptionUpdate::SetToSome(display_name.clone()),
None => OptionUpdate::SetToNone,
Expand Down Expand Up @@ -139,11 +151,11 @@ fn summary_updates_impl(args: Args, state: &RuntimeState) -> Response {
.data
.members
.iter_user_groups()
.filter(|u| u.last_updated() > args.updates_since)
.filter(|u| u.last_updated() > updates_since)
.map(|u| u.into())
.collect(),
user_groups_deleted: state.data.members.user_groups_deleted_since(args.updates_since),
metrics: state.data.cached_chat_metrics.if_set_after(args.updates_since).cloned(),
user_groups_deleted: state.data.members.user_groups_deleted_since(updates_since),
metrics: state.data.cached_chat_metrics.if_set_after(updates_since).cloned(),
})
}

Expand Down
4 changes: 4 additions & 0 deletions backend/canisters/group/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [unreleased]

### Added

- Support getting batches of summary updates via LocalUserIndex ([#4983](https://github.com/open-chat-labs/open-chat/pull/4983))

### Removed

- Removed `local_user_index` endpoint since that is now included in the summary ([#4977](https://github.com/open-chat-labs/open-chat/pull/4977))
Expand Down
9 changes: 8 additions & 1 deletion backend/canisters/group/api/src/queries/c2c_summary.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
pub type Args = super::summary::Args;
use candid::Principal;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub struct Args {
pub on_behalf_of: Option<Principal>,
}

pub type Response = super::summary::Response;
11 changes: 10 additions & 1 deletion backend/canisters/group/api/src/queries/c2c_summary_updates.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
pub type Args = super::summary_updates::Args;
use candid::Principal;
use serde::{Deserialize, Serialize};
use types::TimestampMillis;

#[derive(Serialize, Deserialize, Debug)]
pub struct Args {
pub on_behalf_of: Option<Principal>,
pub updates_since: TimestampMillis,
}

pub type Response = super::summary_updates::Response;
18 changes: 13 additions & 5 deletions backend/canisters/group/impl/src/queries/summary.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
use crate::read_state;
use crate::RuntimeState;
use candid::Principal;
use canister_api_macros::query_msgpack;
use group_canister::c2c_summary::{Args as C2CArgs, Response as C2CResponse};
use group_canister::summary::{Response::*, *};
use ic_cdk_macros::query;

#[query]
fn summary(_: Args) -> Response {
read_state(summary_impl)
read_state(|state| summary_impl(None, state))
}

#[query_msgpack]
fn c2c_summary(_: Args) -> Response {
read_state(summary_impl)
fn c2c_summary(args: C2CArgs) -> C2CResponse {
read_state(|state| summary_impl(args.on_behalf_of, state))
}

fn summary_impl(state: &RuntimeState) -> Response {
let caller = state.env.caller();
fn summary_impl(on_behalf_of: Option<Principal>, state: &RuntimeState) -> Response {
let caller = if let Some(principal) = on_behalf_of {
assert!(state.is_caller_local_user_index());
principal
} else {
state.env.caller()
};

if let Some(member) = state.data.get_member(caller) {
let summary = state.summary(member);
Success(SuccessResult { summary })
Expand Down
38 changes: 24 additions & 14 deletions backend/canisters/group/impl/src/queries/summary_updates.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
use crate::{read_state, RuntimeState};
use candid::Principal;
use canister_api_macros::query_msgpack;
use group_canister::c2c_summary_updates::{Args as C2CArgs, Response as C2CResponse};
use group_canister::summary_updates::{Response::*, *};
use ic_cdk_macros::query;
use types::{GroupCanisterGroupChatSummaryUpdates, GroupMembershipUpdates, OptionUpdate, MAX_THREADS_IN_SUMMARY};
use types::{
GroupCanisterGroupChatSummaryUpdates, GroupMembershipUpdates, OptionUpdate, TimestampMillis, MAX_THREADS_IN_SUMMARY,
};

#[query]
fn summary_updates(args: Args) -> Response {
read_state(|state| summary_updates_impl(args, state))
read_state(|state| summary_updates_impl(args.updates_since, None, state))
}

#[query_msgpack]
fn c2c_summary_updates(args: Args) -> Response {
read_state(|state| summary_updates_impl(args, state))
fn c2c_summary_updates(args: C2CArgs) -> C2CResponse {
read_state(|state| summary_updates_impl(args.updates_since, args.on_behalf_of, state))
}

fn summary_updates_impl(args: Args, state: &RuntimeState) -> Response {
let caller = state.env.caller();
fn summary_updates_impl(updates_since: TimestampMillis, on_behalf_of: Option<Principal>, state: &RuntimeState) -> Response {
let caller = if let Some(principal) = on_behalf_of {
assert!(state.is_caller_local_user_index());
principal
} else {
state.env.caller()
};

let member = match state.data.get_member(caller) {
None => return CallerNotInGroup,
Some(p) => p,
Expand All @@ -24,38 +34,38 @@ fn summary_updates_impl(args: Args, state: &RuntimeState) -> Response {
let chat = &state.data.chat;
let chat_last_updated = chat.last_updated(Some(member.user_id));

if chat_last_updated <= args.updates_since {
if chat_last_updated <= updates_since {
return SuccessNoUpdates;
}

let updates = chat.summary_updates(args.updates_since, Some(member.user_id));
let updates = chat.summary_updates(updates_since, Some(member.user_id));

let membership = GroupMembershipUpdates {
role: updates.role_changed.then_some(member.role.value.into()),
mentions: updates.mentions,
notifications_muted: member.notifications_muted.if_set_after(args.updates_since).cloned(),
notifications_muted: member.notifications_muted.if_set_after(updates_since).cloned(),
my_metrics: state
.data
.chat
.events
.user_metrics(&member.user_id, Some(args.updates_since))
.user_metrics(&member.user_id, Some(updates_since))
.map(|m| m.hydrate()),
latest_threads: chat.events.latest_threads(
member.min_visible_event_index(),
member.threads.iter(),
Some(args.updates_since),
Some(updates_since),
MAX_THREADS_IN_SUMMARY,
member.user_id,
),
unfollowed_threads: chat.events.unfollowed_threads_since(
member.unfollowed_threads.iter(),
args.updates_since,
updates_since,
member.user_id,
),
rules_accepted: member
.rules_accepted
.as_ref()
.filter(|accepted| updates.rules_changed || accepted.timestamp > args.updates_since)
.filter(|accepted| updates.rules_changed || accepted.timestamp > updates_since)
.map(|accepted| accepted.value >= chat.rules.text.version),
};

Expand Down Expand Up @@ -84,7 +94,7 @@ fn summary_updates_impl(args: Args, state: &RuntimeState) -> Response {
frozen: state
.data
.frozen
.if_set_after(args.updates_since)
.if_set_after(updates_since)
.cloned()
.map_or(OptionUpdate::NoChange, OptionUpdate::from_update),
wasm_version: None,
Expand Down
4 changes: 4 additions & 0 deletions backend/canisters/local_user_index/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [unreleased]

### Added

- Support getting batches of summary updates via LocalUserIndex ([#4983](https://github.com/open-chat-labs/open-chat/pull/4983))

### Removed

- Remove code needed to initialise `local_user_index_canister_id` values ([#4981](https://github.com/open-chat-labs/open-chat/pull/4981))
Expand Down
Loading

0 comments on commit e9c7e52

Please sign in to comment.