Skip to content

Commit

Permalink
feat(review): add case-sensitive flag and implement filtering by cate…
Browse files Browse the repository at this point in the history
…gories

Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com>
  • Loading branch information
simonsan committed Mar 8, 2024
1 parent 592fde4 commit ad0f510
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 18 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ toml = { version = "0.8.10", features = ["indexmap", "preserve_order"] }
tracing = { version = "0.1.40" }
typed-builder = "0.18.1"
ulid = { version = "1.1.2", features = ["serde"] }
wildmatch = "2.3.0"

[dev-dependencies]
insta = { version = "1.36.1", features = ["toml", "redactions"] }
Expand Down
11 changes: 9 additions & 2 deletions crates/core/src/commands/review.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use clap::Parser;

use crate::{
domain::review::ReviewFormatKind, get_storage_from_config, get_time_frame_from_flags,
ActivityKind, ActivityStore, ActivityTracker, PaceConfig, PaceResult, UserMessage,
ActivityKind, ActivityStore, ActivityTracker, FilterOptions, PaceConfig, PaceResult,
UserMessage,
};

/// `review` subcommand options
Expand All @@ -29,6 +30,10 @@ pub struct ReviewCommandOptions {
#[cfg_attr(feature = "clap", clap(short, long, name = "Category", alias = "cat"))]
category: Option<String>,

/// Case sensitive category filter
#[cfg_attr(feature = "clap", clap(long, name = "Case Sensitive"))]
case_sensitive: bool,

/// Specify output format (e.g., text, markdown, pdf)
#[cfg_attr(
feature = "clap",
Expand Down Expand Up @@ -80,7 +85,9 @@ impl ReviewCommandOptions {

debug!("Displaying review for time frame: {}", time_frame);

let Some(review_summary) = activity_tracker.generate_review_summary(time_frame)? else {
let Some(review_summary) =
activity_tracker.generate_review_summary(FilterOptions::from(self), time_frame)?
else {
return Ok(UserMessage::new(
"No activities found for the specified time frame",
));
Expand Down
31 changes: 30 additions & 1 deletion crates/core/src/domain/filter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::{ActivityGuid, TimeRangeOptions};
use crate::{ActivityGuid, ReviewCommandOptions, TimeRangeOptions};
use getset::{Getters, MutGetters, Setters};
use serde_derive::Serialize;
use strum::EnumIter;
use typed_builder::TypedBuilder;

/// Filter for activities
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, EnumIter)]
Expand Down Expand Up @@ -81,3 +84,29 @@ impl FilteredActivities {
}
}
}

#[derive(
Debug, TypedBuilder, Serialize, Getters, Setters, MutGetters, Clone, Eq, PartialEq, Default,
)]
#[getset(get = "pub")]
pub struct FilterOptions {
category: Option<String>,
case_sensitive: bool,
}

impl From<ReviewCommandOptions> for FilterOptions {
fn from(options: ReviewCommandOptions) -> Self {
Self {
category: options.category().clone(),
case_sensitive: *options.case_sensitive(),
}
}
}
impl From<&ReviewCommandOptions> for FilterOptions {
fn from(options: &ReviewCommandOptions) -> Self {
Self {
category: options.category().clone(),
case_sensitive: *options.case_sensitive(),
}
}
}
2 changes: 1 addition & 1 deletion crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub use crate::{
},
activity_log::ActivityLog,
category::split_category_by_category_separator,
filter::{ActivityFilterKind, FilteredActivities},
filter::{ActivityFilterKind, FilterOptions, FilteredActivities},
intermission::IntermissionAction,
review::{
Highlights, ReviewSummary, SummaryActivityGroup, SummaryCategories,
Expand Down
37 changes: 27 additions & 10 deletions crates/core/src/service/activity_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use getset::{Getters, MutGetters, Setters};
use tracing::debug;
use typed_builder::TypedBuilder;

use wildmatch::WildMatch;

use crate::{
commands::{resume::ResumeOptions, DeleteOptions, UpdateOptions},
domain::{
Expand All @@ -20,8 +22,8 @@ use crate::{
ActivityQuerying, ActivityReadOps, ActivityStateManagement, ActivityStorage,
ActivityWriteOps, StorageKind, SyncStorage,
},
ActivityGroup, ActivityStatus, EndOptions, HoldOptions, PaceDate, SummaryActivityGroup,
TimeRangeOptions,
ActivityGroup, ActivityStatus, EndOptions, FilterOptions, HoldOptions, PaceDate,
SummaryActivityGroup, TimeRangeOptions,
};

/// The activity store entity
Expand Down Expand Up @@ -100,6 +102,7 @@ impl ActivityStore {
#[tracing::instrument(skip(self))]
pub fn summary_groups_by_category_for_time_range(
&self,
filter_opts: FilterOptions,
time_range_opts: TimeRangeOptions,
) -> PaceOptResult<SummaryGroupByCategory> {
let Some(activity_guids) = self.list_activities_by_time_range(time_range_opts)? else {
Expand All @@ -123,6 +126,26 @@ impl ActivityStore {
for activity_guid in activity_guids {
let activity_item = self.read_activity(activity_guid)?;

let activity_category = activity_item
.activity()
.category()
.as_deref()
.unwrap_or("Uncategorized")
.to_string();

// Skip if category does not match user input
if let Some(category) = filter_opts.category() {
let (filter_category, activity_category) = if *filter_opts.case_sensitive() {
(category.clone(), activity_category.clone())
} else {
(category.to_lowercase(), activity_category.to_lowercase())
};

if !WildMatch::new(&filter_category).matches(&activity_category) {
continue;
}
}

let mut activity_session = ActivitySession::new(activity_item.clone());

if let Some(intermissions) =
Expand All @@ -132,14 +155,8 @@ impl ActivityStore {
};

// Handle splitting subcategories
let (category, subcategory) = category::split_category_by_category_separator(
activity_item
.activity()
.category()
.as_deref()
.unwrap_or("Uncategorized"),
None,
);
let (category, subcategory) =
category::split_category_by_category_separator(&activity_category, None);

// Deduplicate activities by category and description first
_ = activity_sessions_lookup_by_category
Expand Down
7 changes: 5 additions & 2 deletions crates/core/src/service/activity_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

use tracing::debug;

use crate::{ActivityStore, PaceOptResult, PaceTimeFrame, ReviewSummary, TimeRangeOptions};
use crate::{
ActivityStore, FilterOptions, PaceOptResult, PaceTimeFrame, ReviewSummary, TimeRangeOptions,
};

// This struct represents the overall structure for tracking activities and their intermissions.
pub struct ActivityTracker {
Expand All @@ -25,13 +27,14 @@ impl ActivityTracker {
#[tracing::instrument(skip(self))]
pub fn generate_review_summary(
&self,
filter_opts: FilterOptions,
time_frame: PaceTimeFrame,
) -> PaceOptResult<ReviewSummary> {
let time_range_opts = TimeRangeOptions::try_from(time_frame)?;

let Some(summary_groups) = self
.store
.summary_groups_by_category_for_time_range(time_range_opts)?
.summary_groups_by_category_for_time_range(filter_opts, time_range_opts)?
else {
return Ok(None);
};
Expand Down
6 changes: 4 additions & 2 deletions crates/core/tests/activity_tracker.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Test the `ActivityStore` implementation with a `InMemoryStorage` backend.

use pace_core::{ActivityStore, ActivityTracker, PaceDuration, TestResult, TimeRangeOptions};
use pace_core::{
ActivityStore, ActivityTracker, FilterOptions, PaceDuration, TestResult, TimeRangeOptions,
};
use rstest::rstest;
use similar_asserts::assert_eq;

Expand All @@ -23,7 +25,7 @@ fn test_activity_tracker(

let summary_groups_by_category = activity_tracker
.store
.summary_groups_by_category_for_time_range(time_range_opts)?
.summary_groups_by_category_for_time_range(FilterOptions::default(), time_range_opts)?
.ok_or("Should have dates.")?;

assert_eq!(
Expand Down

0 comments on commit ad0f510

Please sign in to comment.