Skip to content

Commit ec29c7a

Browse files
authored
Merge pull request #137 from rambit-systems/push-rpqpottvqmqr
Incremental UI Push #21
2 parents 68de4d5 + b301111 commit ec29c7a

File tree

7 files changed

+136
-31
lines changed

7 files changed

+136
-31
lines changed

crates/prime-domain/src/counts.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use db::{FetchModelByIndexError, kv::LaxSlug};
2-
use models::{EntryIndexSelector, Store, dvf::RecordId};
2+
use models::{Cache, EntryIndexSelector, Store, dvf::RecordId};
33

44
use crate::PrimeDomainService;
55

@@ -17,4 +17,18 @@ impl PrimeDomainService {
1717
)
1818
.await
1919
}
20+
21+
/// Counts the number of [`Entry`](models::Entry)s in a [`Cache`].
22+
pub async fn count_entries_in_cache(
23+
&self,
24+
cache: RecordId<Cache>,
25+
) -> Result<u32, FetchModelByIndexError> {
26+
self
27+
.entry_repo
28+
.count_models_by_index(
29+
EntryIndexSelector::Cache,
30+
LaxSlug::new(cache.to_string()).into(),
31+
)
32+
.await
33+
}
2034
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use std::fmt;
2+
3+
pub struct ThousandsSeparated<T>(pub T);
4+
5+
impl<T: Into<u64> + Copy> fmt::Display for ThousandsSeparated<T> {
6+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7+
write!(f, "{}", format_with_commas(self.0.into()))
8+
}
9+
}
10+
11+
fn format_with_commas(n: u64) -> String {
12+
let s = n.to_string();
13+
let chars: Vec<char> = s.chars().collect();
14+
let mut result = String::new();
15+
16+
for (i, ch) in chars.iter().enumerate() {
17+
if i > 0 && (chars.len() - i).is_multiple_of(3) {
18+
result.push(',');
19+
}
20+
result.push(*ch);
21+
}
22+
23+
result
24+
}

crates/site-app/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![feature(iter_intersperse)]
33

44
mod components;
5+
mod formatting_utils;
56
mod hooks;
67
mod join_classes;
78
mod navigation;

crates/site-app/src/pages/create_cache/visibility_selector.rs

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,41 @@ pub(super) fn VisibilitySelector(
1515
let set_private = move |_| signal.set(Visibility::Private);
1616
let set_public = move |_| signal.set(Visibility::Public);
1717

18-
const OPTION_CLASS: &str = "flex-1 flex flex-col gap-2 px-4 py-3 \
19-
hover:elevation-lv1 transition-shadow \
20-
transition-colors rounded border-2 \
21-
border-base-7 hover:border-base-8";
22-
const TITLE_CLASS: &str = "text-lg font-semibold leading-none";
18+
const OUTER_CLASS: &str = "flex-1 flex flex-col gap-2 px-4 py-3 \
19+
hover:elevation-lv1 transition rounded border-2 \
20+
border-base-7 hover:border-base-8 \
21+
bg-gradient-to-tr to-transparent to-50%";
22+
const OUTER_ACTIVE_CLASS: &str =
23+
"border-product-7 hover:border-product-8 from-product-3";
24+
const OUTER_INACTIVE_CLASS: &str = "from-transparent";
25+
const TITLE_CLASS: &str = "text-base-12 text-lg font-semibold leading-none";
2326
const DESCRIPTION_CLASS: &str = "text-sm leading-[1.1]";
2427

28+
let outer_private_class = move || {
29+
format!(
30+
"{OUTER_CLASS} {}",
31+
if is_private() {
32+
OUTER_ACTIVE_CLASS
33+
} else {
34+
OUTER_INACTIVE_CLASS
35+
}
36+
)
37+
};
38+
let outer_public_class = move || {
39+
format!(
40+
"{OUTER_CLASS} {}",
41+
if is_public() {
42+
OUTER_ACTIVE_CLASS
43+
} else {
44+
OUTER_INACTIVE_CLASS
45+
}
46+
)
47+
};
48+
2549
view! {
26-
<div class="flex flex-row gap-4">
50+
<div class="flex flex-col sm:flex-row gap-4">
2751
<div
28-
class=OPTION_CLASS
29-
class=("border-product-7", is_private)
30-
class=("hover:border-product-8", is_private)
52+
class=outer_private_class
3153
on:click=set_private
3254
>
3355
<div class="flex flex-row justify-between items-center">
@@ -49,9 +71,7 @@ pub(super) fn VisibilitySelector(
4971
</div>
5072

5173
<div
52-
class=OPTION_CLASS
53-
class=("border-product-7", is_public)
54-
class=("hover:border-product-8", is_public)
74+
class=outer_public_class
5575
on:click=set_public
5676
>
5777
<div class="flex flex-row justify-between items-center">

crates/site-app/src/pages/dashboard/cache.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ use crate::{
77
CacheItemLink, CreateCacheButton, DataTableRefreshButton,
88
LockClosedHeroIcon,
99
},
10+
formatting_utils::ThousandsSeparated,
1011
hooks::OrgHook,
11-
resources::cache::caches_in_org_query_scope,
12+
resources::cache::{
13+
caches_in_org_query_scope, entry_count_in_cache_query_scope,
14+
},
1215
};
1316

1417
#[island]
@@ -51,6 +54,7 @@ pub(super) fn CacheTable() -> impl IntoView {
5154
<thead>
5255
<th>"Name"</th>
5356
<th>"Visibility"</th>
57+
<th>"Entry Count"</th>
5458
</thead>
5559
<Transition fallback=|| ()>
5660
{ suspend }
@@ -62,6 +66,21 @@ pub(super) fn CacheTable() -> impl IntoView {
6266

6367
#[component]
6468
fn CacheDataRow(cache: PvCache) -> impl IntoView {
69+
let query_client = expect_context::<QueryClient>();
70+
71+
let entry_count_query_scope = entry_count_in_cache_query_scope();
72+
let entry_count_key = move || cache.id;
73+
let entry_count_resource =
74+
query_client.resource(entry_count_query_scope, entry_count_key);
75+
let entry_count_suspend = move || {
76+
Suspend::new(async move {
77+
match entry_count_resource.await {
78+
Ok(count) => ThousandsSeparated(count).to_string().into_any(),
79+
Err(_) => "[error]".into_any(),
80+
}
81+
})
82+
};
83+
6584
view! {
6685
<tr>
6786
<th scope="row"><CacheItemLink id=cache.id extra_class="text-link-primary"/></th>
@@ -71,6 +90,9 @@ fn CacheDataRow(cache: PvCache) -> impl IntoView {
7190
<LockClosedHeroIcon {..} class="size-4 stroke-base-11/75 stroke-[2.0]" />
7291
})}
7392
</td>
93+
<td><Transition fallback=|| "[loading]">
94+
{ entry_count_suspend }
95+
</Transition></td>
7496
</tr>
7597
}
7698
}

crates/site-app/src/pages/dashboard/store.rs

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use models::{PvStorageCredentials, PvStore};
44

55
use crate::{
66
components::{DataTableRefreshButton, StoreItemLink},
7+
formatting_utils::ThousandsSeparated,
78
hooks::OrgHook,
89
resources::store::{
910
entry_count_in_store_query_scope, stores_in_org_query_scope,
@@ -69,7 +70,7 @@ fn StoreDataRow(store: PvStore) -> impl IntoView {
6970
let entry_count_suspend = move || {
7071
Suspend::new(async move {
7172
match entry_count_resource.await {
72-
Ok(count) => format_with_commas(count).into_any(),
73+
Ok(count) => ThousandsSeparated(count).to_string().into_any(),
7374
Err(_) => "[error]".into_any(),
7475
}
7576
})
@@ -91,18 +92,3 @@ fn StoreDataRow(store: PvStore) -> impl IntoView {
9192
</tr>
9293
}
9394
}
94-
95-
fn format_with_commas(n: u32) -> String {
96-
let s = n.to_string();
97-
let chars: Vec<char> = s.chars().collect();
98-
let mut result = String::new();
99-
100-
for (i, ch) in chars.iter().enumerate() {
101-
if i > 0 && (chars.len() - i).is_multiple_of(3) {
102-
result.push(',');
103-
}
104-
result.push(*ch);
105-
}
106-
107-
result
108-
}

crates/site-app/src/resources/cache.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use leptos::prelude::*;
22
use leptos_fetch::{QueryClient, QueryScope};
3-
use models::{dvf::RecordId, model::Model, Cache, Org, PvCache};
3+
use models::{dvf::RecordId, model::Model, Cache, Entry, Org, PvCache};
44

55
#[cfg(feature = "ssr")]
66
use crate::resources::{authenticate, authorize_for_org};
@@ -122,3 +122,41 @@ pub async fn check_if_cache_name_is_available(
122122

123123
Ok(!occupied)
124124
}
125+
126+
pub fn entry_count_in_cache_query_scope(
127+
) -> QueryScope<RecordId<Cache>, Result<u32, ServerFnError>> {
128+
QueryScope::new(count_entries_in_cache).with_invalidation_link(move |s| {
129+
[
130+
Cache::TABLE_NAME.to_string(),
131+
s.to_string(),
132+
Entry::TABLE_NAME.to_string(),
133+
]
134+
})
135+
}
136+
137+
#[server(prefix = "/api/sfn")]
138+
pub async fn count_entries_in_cache(
139+
cache: RecordId<Cache>,
140+
) -> Result<u32, ServerFnError> {
141+
use prime_domain::PrimeDomainService;
142+
143+
let prime_domain_service: PrimeDomainService = expect_context();
144+
let cache = prime_domain_service
145+
.fetch_cache_by_id(cache)
146+
.await
147+
.map_err(|e| {
148+
tracing::error!("failed to fetch cache: {e}");
149+
ServerFnError::new("internal error")
150+
})?
151+
.ok_or(ServerFnError::new("cache does not exist"))?;
152+
153+
authorize_for_org(cache.org)?;
154+
155+
prime_domain_service
156+
.count_entries_in_cache(cache.id)
157+
.await
158+
.map_err(|e| {
159+
tracing::error!("failed to count entries in cache ({}): {e}", cache.id);
160+
ServerFnError::new("internal error")
161+
})
162+
}

0 commit comments

Comments
 (0)