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
16 changes: 15 additions & 1 deletion crates/prime-domain/src/counts.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use db::{FetchModelByIndexError, kv::LaxSlug};
use models::{EntryIndexSelector, Store, dvf::RecordId};
use models::{Cache, EntryIndexSelector, Store, dvf::RecordId};

use crate::PrimeDomainService;

Expand All @@ -17,4 +17,18 @@ impl PrimeDomainService {
)
.await
}

/// Counts the number of [`Entry`](models::Entry)s in a [`Cache`].
pub async fn count_entries_in_cache(
&self,
cache: RecordId<Cache>,
) -> Result<u32, FetchModelByIndexError> {
self
.entry_repo
.count_models_by_index(
EntryIndexSelector::Cache,
LaxSlug::new(cache.to_string()).into(),
)
.await
}
}
24 changes: 24 additions & 0 deletions crates/site-app/src/formatting_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use std::fmt;

pub struct ThousandsSeparated<T>(pub T);

impl<T: Into<u64> + Copy> fmt::Display for ThousandsSeparated<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", format_with_commas(self.0.into()))
}
}

fn format_with_commas(n: u64) -> String {
let s = n.to_string();
let chars: Vec<char> = s.chars().collect();
let mut result = String::new();

for (i, ch) in chars.iter().enumerate() {
if i > 0 && (chars.len() - i).is_multiple_of(3) {
result.push(',');
}
result.push(*ch);
}

result
}
1 change: 1 addition & 0 deletions crates/site-app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#![feature(iter_intersperse)]

mod components;
mod formatting_utils;
mod hooks;
mod join_classes;
mod navigation;
Expand Down
44 changes: 32 additions & 12 deletions crates/site-app/src/pages/create_cache/visibility_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,41 @@ pub(super) fn VisibilitySelector(
let set_private = move |_| signal.set(Visibility::Private);
let set_public = move |_| signal.set(Visibility::Public);

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

let outer_private_class = move || {
format!(
"{OUTER_CLASS} {}",
if is_private() {
OUTER_ACTIVE_CLASS
} else {
OUTER_INACTIVE_CLASS
}
)
};
let outer_public_class = move || {
format!(
"{OUTER_CLASS} {}",
if is_public() {
OUTER_ACTIVE_CLASS
} else {
OUTER_INACTIVE_CLASS
}
)
};

view! {
<div class="flex flex-row gap-4">
<div class="flex flex-col sm:flex-row gap-4">
<div
class=OPTION_CLASS
class=("border-product-7", is_private)
class=("hover:border-product-8", is_private)
class=outer_private_class
on:click=set_private
>
<div class="flex flex-row justify-between items-center">
Expand All @@ -49,9 +71,7 @@ pub(super) fn VisibilitySelector(
</div>

<div
class=OPTION_CLASS
class=("border-product-7", is_public)
class=("hover:border-product-8", is_public)
class=outer_public_class
on:click=set_public
>
<div class="flex flex-row justify-between items-center">
Expand Down
24 changes: 23 additions & 1 deletion crates/site-app/src/pages/dashboard/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ use crate::{
CacheItemLink, CreateCacheButton, DataTableRefreshButton,
LockClosedHeroIcon,
},
formatting_utils::ThousandsSeparated,
hooks::OrgHook,
resources::cache::caches_in_org_query_scope,
resources::cache::{
caches_in_org_query_scope, entry_count_in_cache_query_scope,
},
};

#[island]
Expand Down Expand Up @@ -51,6 +54,7 @@ pub(super) fn CacheTable() -> impl IntoView {
<thead>
<th>"Name"</th>
<th>"Visibility"</th>
<th>"Entry Count"</th>
</thead>
<Transition fallback=|| ()>
{ suspend }
Expand All @@ -62,6 +66,21 @@ pub(super) fn CacheTable() -> impl IntoView {

#[component]
fn CacheDataRow(cache: PvCache) -> impl IntoView {
let query_client = expect_context::<QueryClient>();

let entry_count_query_scope = entry_count_in_cache_query_scope();
let entry_count_key = move || cache.id;
let entry_count_resource =
query_client.resource(entry_count_query_scope, entry_count_key);
let entry_count_suspend = move || {
Suspend::new(async move {
match entry_count_resource.await {
Ok(count) => ThousandsSeparated(count).to_string().into_any(),
Err(_) => "[error]".into_any(),
}
})
};

view! {
<tr>
<th scope="row"><CacheItemLink id=cache.id extra_class="text-link-primary"/></th>
Expand All @@ -71,6 +90,9 @@ fn CacheDataRow(cache: PvCache) -> impl IntoView {
<LockClosedHeroIcon {..} class="size-4 stroke-base-11/75 stroke-[2.0]" />
})}
</td>
<td><Transition fallback=|| "[loading]">
{ entry_count_suspend }
</Transition></td>
</tr>
}
}
18 changes: 2 additions & 16 deletions crates/site-app/src/pages/dashboard/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use models::{PvStorageCredentials, PvStore};

use crate::{
components::{DataTableRefreshButton, StoreItemLink},
formatting_utils::ThousandsSeparated,
hooks::OrgHook,
resources::store::{
entry_count_in_store_query_scope, stores_in_org_query_scope,
Expand Down Expand Up @@ -69,7 +70,7 @@ fn StoreDataRow(store: PvStore) -> impl IntoView {
let entry_count_suspend = move || {
Suspend::new(async move {
match entry_count_resource.await {
Ok(count) => format_with_commas(count).into_any(),
Ok(count) => ThousandsSeparated(count).to_string().into_any(),
Err(_) => "[error]".into_any(),
}
})
Expand All @@ -91,18 +92,3 @@ fn StoreDataRow(store: PvStore) -> impl IntoView {
</tr>
}
}

fn format_with_commas(n: u32) -> String {
let s = n.to_string();
let chars: Vec<char> = s.chars().collect();
let mut result = String::new();

for (i, ch) in chars.iter().enumerate() {
if i > 0 && (chars.len() - i).is_multiple_of(3) {
result.push(',');
}
result.push(*ch);
}

result
}
40 changes: 39 additions & 1 deletion crates/site-app/src/resources/cache.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use leptos::prelude::*;
use leptos_fetch::{QueryClient, QueryScope};
use models::{dvf::RecordId, model::Model, Cache, Org, PvCache};
use models::{dvf::RecordId, model::Model, Cache, Entry, Org, PvCache};

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

Ok(!occupied)
}

pub fn entry_count_in_cache_query_scope(
) -> QueryScope<RecordId<Cache>, Result<u32, ServerFnError>> {
QueryScope::new(count_entries_in_cache).with_invalidation_link(move |s| {
[
Cache::TABLE_NAME.to_string(),
s.to_string(),
Entry::TABLE_NAME.to_string(),
]
})
}

#[server(prefix = "/api/sfn")]
pub async fn count_entries_in_cache(
cache: RecordId<Cache>,
) -> Result<u32, ServerFnError> {
use prime_domain::PrimeDomainService;

let prime_domain_service: PrimeDomainService = expect_context();
let cache = prime_domain_service
.fetch_cache_by_id(cache)
.await
.map_err(|e| {
tracing::error!("failed to fetch cache: {e}");
ServerFnError::new("internal error")
})?
.ok_or(ServerFnError::new("cache does not exist"))?;

authorize_for_org(cache.org)?;

prime_domain_service
.count_entries_in_cache(cache.id)
.await
.map_err(|e| {
tracing::error!("failed to count entries in cache ({}): {e}", cache.id);
ServerFnError::new("internal error")
})
}