use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use ustr::Ustr;
use crate::data::GetMetadata;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum FilterOp {
All,
Any,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum FilterType {
Include,
Exclude,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum KeyValueFilter {
CourseFilter {
key: String,
value: String,
filter_type: FilterType,
},
LessonFilter {
key: String,
value: String,
filter_type: FilterType,
},
CombinedFilter {
op: FilterOp,
filters: Vec<KeyValueFilter>,
},
}
impl KeyValueFilter {
fn passes_filter(
metadata: &BTreeMap<String, Vec<String>>,
key: &str,
value: &str,
filter_type: &FilterType,
) -> bool {
let contains_metadata = if metadata.contains_key(key) {
metadata
.get(key)
.unwrap_or(&Vec::new())
.contains(&value.to_string())
} else {
false
};
match filter_type {
FilterType::Include => contains_metadata,
FilterType::Exclude => !contains_metadata,
}
}
pub fn apply_to_course(&self, course_manifest: &impl GetMetadata) -> bool {
let default_metadata = BTreeMap::default();
let course_metadata = course_manifest.get_metadata().unwrap_or(&default_metadata);
match self {
KeyValueFilter::CourseFilter {
key,
value,
filter_type,
} => {
KeyValueFilter::passes_filter(course_metadata, key, value, filter_type)
}
KeyValueFilter::LessonFilter { .. } => {
false
}
KeyValueFilter::CombinedFilter { op, filters } => {
let course_filters = filters
.iter()
.filter(|f| matches!(f, KeyValueFilter::CourseFilter { .. }))
.collect::<Vec<_>>();
let other_filters = filters
.iter()
.filter(|f| !matches!(f, KeyValueFilter::CourseFilter { .. }))
.collect::<Vec<_>>();
let course_result = match *op {
FilterOp::All => course_filters
.iter()
.all(|f| f.apply_to_course(course_manifest)),
FilterOp::Any => course_filters
.iter()
.any(|f| f.apply_to_course(course_manifest)),
};
let other_result = match *op {
FilterOp::All => other_filters
.iter()
.all(|f| f.apply_to_course(course_manifest)),
FilterOp::Any => other_filters
.iter()
.any(|f| f.apply_to_course(course_manifest)),
};
if other_filters.is_empty() {
return course_result;
}
match *op {
FilterOp::All => course_result && other_result,
FilterOp::Any => false,
}
}
}
}
pub fn apply_to_lesson(
&self,
course_manifest: &impl GetMetadata,
lesson_manifest: &impl GetMetadata,
) -> bool {
let default_metadata = BTreeMap::default();
let course_metadata = course_manifest.get_metadata().unwrap_or(&default_metadata);
let lesson_metadata = lesson_manifest.get_metadata().unwrap_or(&default_metadata);
match self {
KeyValueFilter::CourseFilter {
key,
value,
filter_type,
} => {
KeyValueFilter::passes_filter(course_metadata, key, value, filter_type)
}
KeyValueFilter::LessonFilter {
key,
value,
filter_type,
} => {
KeyValueFilter::passes_filter(lesson_metadata, key, value, filter_type)
}
KeyValueFilter::CombinedFilter { op, filters } => {
match *op {
FilterOp::All => filters
.iter()
.all(|f| f.apply_to_lesson(course_manifest, lesson_manifest)),
FilterOp::Any => filters
.iter()
.any(|f| f.apply_to_lesson(course_manifest, lesson_manifest)),
}
}
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum UnitFilter {
CourseFilter {
course_ids: Vec<Ustr>,
},
LessonFilter {
lesson_ids: Vec<Ustr>,
},
MetadataFilter {
filter: KeyValueFilter,
},
ReviewListFilter,
Dependents {
unit_ids: Vec<Ustr>,
},
Dependencies {
unit_ids: Vec<Ustr>,
depth: usize,
},
}
impl UnitFilter {
#[must_use]
pub fn passes_course_filter(&self, course_id: &Ustr) -> bool {
match self {
UnitFilter::CourseFilter { course_ids } => course_ids.contains(course_id),
_ => false,
}
}
#[must_use]
pub fn passes_lesson_filter(&self, lesson_id: &Ustr) -> bool {
match self {
UnitFilter::LessonFilter { lesson_ids } => lesson_ids.contains(lesson_id),
_ => false,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct SavedFilter {
pub id: String,
pub description: String,
pub filter: UnitFilter,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum SessionPart {
UnitFilter {
filter: UnitFilter,
duration: u32,
},
SavedFilter {
filter_id: String,
duration: u32,
},
NoFilter {
duration: u32,
},
}
impl SessionPart {
#[must_use]
pub fn duration(&self) -> u32 {
match self {
SessionPart::UnitFilter { duration, .. }
| SessionPart::SavedFilter { duration, .. }
| SessionPart::NoFilter { duration, .. } => *duration,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct StudySession {
pub id: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub parts: Vec<SessionPart>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct StudySessionData {
pub start_time: DateTime<Utc>,
pub definition: StudySession,
}
impl StudySessionData {
#[must_use]
pub fn get_part(&self, time: DateTime<Utc>) -> SessionPart {
if self.definition.parts.is_empty() {
return SessionPart::NoFilter { duration: 0 };
}
let minutes_since_start = (time - self.start_time).num_minutes();
if minutes_since_start < 0 {
return self.definition.parts[0].clone();
}
let minutes_since_start = minutes_since_start as u32;
let mut session_length = 0;
for part in &self.definition.parts {
session_length += part.duration();
if minutes_since_start < session_length {
return part.clone();
}
}
self.definition.parts.last().unwrap().clone()
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum ExerciseFilter {
UnitFilter(UnitFilter),
StudySession(StudySessionData),
}
#[cfg(test)]
#[cfg_attr(coverage, coverage(off))]
mod test {
use chrono::{Duration, Utc};
use std::collections::BTreeMap;
use ustr::Ustr;
use crate::data::{
GetMetadata,
filter::{FilterOp, FilterType, KeyValueFilter, SessionPart, StudySessionData, UnitFilter},
};
use super::StudySession;
impl GetMetadata for BTreeMap<String, Vec<String>> {
fn get_metadata(&self) -> Option<&BTreeMap<String, Vec<String>>> {
Some(self)
}
}
#[test]
fn passes_course_filter() {
let filter = UnitFilter::CourseFilter {
course_ids: vec!["course1".into()],
};
assert!(filter.passes_course_filter(&"course1".into()));
assert!(!filter.passes_course_filter(&"course2".into()));
assert!(!filter.passes_lesson_filter(&"lesson1".into()));
}
#[test]
fn passes_lesson_filter() {
let filter = UnitFilter::LessonFilter {
lesson_ids: vec!["lesson1".into()],
};
assert!(filter.passes_lesson_filter(&"lesson1".into()));
assert!(!filter.passes_lesson_filter(&"lesson2".into()));
assert!(!filter.passes_course_filter(&"course1".into()));
}
#[test]
fn apply_course_filter_to_course() {
let metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let include_filter = KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Include,
};
assert!(include_filter.apply_to_course(&metadata));
let exclude_filter = KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Exclude,
};
assert!(!exclude_filter.apply_to_course(&metadata));
}
#[test]
fn apply_lesson_filter_to_course() {
let metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let include_filter = KeyValueFilter::LessonFilter {
key: "key1".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Include,
};
assert!(!include_filter.apply_to_course(&metadata));
let exclude_filter = KeyValueFilter::LessonFilter {
key: "key1".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Exclude,
};
assert!(!exclude_filter.apply_to_course(&metadata));
}
#[test]
fn apply_course_filter_to_course_no_match() {
let metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let include_filter = KeyValueFilter::CourseFilter {
key: "key10".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Include,
};
assert!(!include_filter.apply_to_course(&metadata));
let exclude_filter = KeyValueFilter::CourseFilter {
key: "key10".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Exclude,
};
assert!(exclude_filter.apply_to_course(&metadata));
}
#[test]
fn apply_course_filter_to_lesson() {
let course_metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let lesson_metadata = BTreeMap::from([
(
"key3".to_string(),
vec!["value5".to_string(), "value6".to_string()],
),
(
"key4".to_string(),
vec!["value7".to_string(), "value8".to_string()],
),
]);
let include_filter = KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Include,
};
assert!(include_filter.apply_to_lesson(&course_metadata, &lesson_metadata));
let exclude_filter = KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Exclude,
};
assert!(!exclude_filter.apply_to_lesson(&course_metadata, &lesson_metadata));
}
#[test]
fn apply_lesson_filter_to_lesson() {
let course_metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let lesson_metadata = BTreeMap::from([
(
"key3".to_string(),
vec!["value5".to_string(), "value6".to_string()],
),
(
"key4".to_string(),
vec!["value7".to_string(), "value8".to_string()],
),
]);
let include_filter = KeyValueFilter::LessonFilter {
key: "key3".to_string(),
value: "value5".to_string(),
filter_type: FilterType::Include,
};
assert!(include_filter.apply_to_lesson(&course_metadata, &lesson_metadata));
let exclude_filter = KeyValueFilter::LessonFilter {
key: "key3".to_string(),
value: "value5".to_string(),
filter_type: FilterType::Exclude,
};
assert!(!exclude_filter.apply_to_lesson(&course_metadata, &lesson_metadata));
}
#[test]
fn apply_course_filter_to_lesson_no_match() {
let course_metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let lesson_metadata = BTreeMap::from([
(
"key3".to_string(),
vec!["value5".to_string(), "value6".to_string()],
),
(
"key4".to_string(),
vec!["value7".to_string(), "value8".to_string()],
),
]);
let include_filter = KeyValueFilter::CourseFilter {
key: "key3".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Include,
};
assert!(!include_filter.apply_to_lesson(&course_metadata, &lesson_metadata));
let exclude_filter = KeyValueFilter::CourseFilter {
key: "key3".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Exclude,
};
assert!(exclude_filter.apply_to_lesson(&course_metadata, &lesson_metadata));
}
#[test]
fn apply_lesson_filter_to_lesson_no_match() {
let course_metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let lesson_metadata = BTreeMap::from([
(
"key3".to_string(),
vec!["value5".to_string(), "value6".to_string()],
),
(
"key4".to_string(),
vec!["value7".to_string(), "value8".to_string()],
),
]);
let include_filter = KeyValueFilter::LessonFilter {
key: "key2".to_string(),
value: "value5".to_string(),
filter_type: FilterType::Include,
};
assert!(!include_filter.apply_to_lesson(&course_metadata, &lesson_metadata));
let exclude_filter = KeyValueFilter::LessonFilter {
key: "key2".to_string(),
value: "value5".to_string(),
filter_type: FilterType::Exclude,
};
assert!(exclude_filter.apply_to_lesson(&course_metadata, &lesson_metadata));
}
#[test]
fn apply_combined_all_filter_to_course() {
let metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let filter = KeyValueFilter::CombinedFilter {
op: FilterOp::All,
filters: vec![
KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Include,
},
KeyValueFilter::CourseFilter {
key: "key2".to_string(),
value: "value4".to_string(),
filter_type: FilterType::Include,
},
],
};
assert!(filter.apply_to_course(&metadata));
}
#[test]
fn apply_combined_all_filter_with_lesson_filter_to_course() {
let metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let filter = KeyValueFilter::CombinedFilter {
op: FilterOp::All,
filters: vec![
KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Include,
},
KeyValueFilter::CourseFilter {
key: "key2".to_string(),
value: "value4".to_string(),
filter_type: FilterType::Include,
},
KeyValueFilter::LessonFilter {
key: "key3".to_string(),
value: "value5".to_string(),
filter_type: FilterType::Include,
},
],
};
assert!(!filter.apply_to_course(&metadata));
}
#[test]
fn apply_combined_all_filter_with_combined_filter_to_course() {
let metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let filter = KeyValueFilter::CombinedFilter {
op: FilterOp::All,
filters: vec![
KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Include,
},
KeyValueFilter::CourseFilter {
key: "key2".to_string(),
value: "value4".to_string(),
filter_type: FilterType::Include,
},
KeyValueFilter::CombinedFilter {
op: FilterOp::All,
filters: vec![KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value2".to_string(),
filter_type: FilterType::Include,
}],
},
],
};
assert!(filter.apply_to_course(&metadata));
}
#[test]
fn apply_combined_all_filter_to_course_no_match() {
let metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let filter = KeyValueFilter::CombinedFilter {
op: FilterOp::All,
filters: vec![
KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Include,
},
KeyValueFilter::CourseFilter {
key: "key2".to_string(),
value: "value5".to_string(),
filter_type: FilterType::Include,
},
],
};
assert!(!filter.apply_to_course(&metadata));
}
#[test]
fn apply_combined_any_filter_to_course() {
let metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let filter = KeyValueFilter::CombinedFilter {
op: FilterOp::Any,
filters: vec![
KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Include,
},
KeyValueFilter::CourseFilter {
key: "key2".to_string(),
value: "value5".to_string(),
filter_type: FilterType::Include,
},
],
};
assert!(filter.apply_to_course(&metadata));
}
#[test]
fn apply_combined_any_filter_to_course_no_match() {
let metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let filter = KeyValueFilter::CombinedFilter {
op: FilterOp::Any,
filters: vec![
KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value3".to_string(),
filter_type: FilterType::Include,
},
KeyValueFilter::CourseFilter {
key: "key2".to_string(),
value: "value5".to_string(),
filter_type: FilterType::Include,
},
],
};
assert!(!filter.apply_to_course(&metadata));
}
#[test]
fn apply_combined_any_filter_with_combined_filter_to_course() {
let metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let filter = KeyValueFilter::CombinedFilter {
op: FilterOp::Any,
filters: vec![
KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Include,
},
KeyValueFilter::CourseFilter {
key: "key2".to_string(),
value: "value4".to_string(),
filter_type: FilterType::Include,
},
KeyValueFilter::CombinedFilter {
op: FilterOp::All,
filters: vec![KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value2".to_string(),
filter_type: FilterType::Include,
}],
},
],
};
assert!(!filter.apply_to_course(&metadata));
}
#[test]
fn apply_combined_all_filter_to_lesson() {
let course_metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let lesson_metadata = BTreeMap::from([
(
"key3".to_string(),
vec!["value5".to_string(), "value6".to_string()],
),
(
"key4".to_string(),
vec!["value7".to_string(), "value8".to_string()],
),
]);
let filter = KeyValueFilter::CombinedFilter {
op: FilterOp::All,
filters: vec![
KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Include,
},
KeyValueFilter::LessonFilter {
key: "key3".to_string(),
value: "value6".to_string(),
filter_type: FilterType::Include,
},
],
};
assert!(filter.apply_to_lesson(&course_metadata, &lesson_metadata));
}
#[test]
fn apply_combined_all_filter_to_lesson_no_match() {
let course_metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let lesson_metadata = BTreeMap::from([
(
"key3".to_string(),
vec!["value5".to_string(), "value6".to_string()],
),
(
"key4".to_string(),
vec!["value7".to_string(), "value8".to_string()],
),
]);
let filter = KeyValueFilter::CombinedFilter {
op: FilterOp::All,
filters: vec![
KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value8".to_string(),
filter_type: FilterType::Include,
},
KeyValueFilter::LessonFilter {
key: "key3".to_string(),
value: "value6".to_string(),
filter_type: FilterType::Include,
},
],
};
assert!(!filter.apply_to_lesson(&course_metadata, &lesson_metadata));
}
#[test]
fn apply_combined_any_filter_to_lesson() {
let course_metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let lesson_metadata = BTreeMap::from([
(
"key3".to_string(),
vec!["value5".to_string(), "value6".to_string()],
),
(
"key4".to_string(),
vec!["value7".to_string(), "value8".to_string()],
),
]);
let filter = KeyValueFilter::CombinedFilter {
op: FilterOp::Any,
filters: vec![
KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Include,
},
KeyValueFilter::LessonFilter {
key: "key3".to_string(),
value: "value1".to_string(),
filter_type: FilterType::Include,
},
],
};
assert!(filter.apply_to_lesson(&course_metadata, &lesson_metadata));
}
#[test]
fn apply_combined_any_filter_to_lesson_no_match() {
let course_metadata = BTreeMap::from([
(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
),
(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
),
]);
let lesson_metadata = BTreeMap::from([
(
"key3".to_string(),
vec!["value5".to_string(), "value6".to_string()],
),
(
"key4".to_string(),
vec!["value7".to_string(), "value8".to_string()],
),
]);
let filter = KeyValueFilter::CombinedFilter {
op: FilterOp::Any,
filters: vec![
KeyValueFilter::CourseFilter {
key: "key1".to_string(),
value: "value8".to_string(),
filter_type: FilterType::Include,
},
KeyValueFilter::LessonFilter {
key: "key3".to_string(),
value: "value2".to_string(),
filter_type: FilterType::Include,
},
],
};
assert!(!filter.apply_to_lesson(&course_metadata, &lesson_metadata));
}
#[test]
fn unit_filter_clone() {
let filter = UnitFilter::CourseFilter {
course_ids: vec![Ustr::from("course1")],
};
assert_eq!(filter.clone(), filter);
let filter = UnitFilter::LessonFilter {
lesson_ids: vec![Ustr::from("lesson1")],
};
assert_eq!(filter.clone(), filter);
let filter = UnitFilter::MetadataFilter {
filter: KeyValueFilter::CourseFilter {
key: "key".into(),
value: "value".into(),
filter_type: FilterType::Include,
},
};
assert_eq!(filter.clone(), filter);
let filter = UnitFilter::ReviewListFilter;
assert_eq!(filter.clone(), filter);
}
#[test]
fn get_session_part() {
let session_data = StudySessionData {
definition: StudySession {
id: "session".into(),
description: "session".into(),
parts: vec![],
},
start_time: Utc::now(),
};
assert_eq!(
session_data.get_part(Utc::now()),
SessionPart::NoFilter { duration: 0 }
);
let start_time = Utc::now();
let session_data = StudySessionData {
definition: StudySession {
id: "session".into(),
description: "session".into(),
parts: vec![
SessionPart::SavedFilter {
filter_id: "1".into(),
duration: 1,
},
SessionPart::SavedFilter {
filter_id: "2".into(),
duration: 1,
},
SessionPart::SavedFilter {
filter_id: "3".into(),
duration: 1,
},
],
},
start_time,
};
assert_eq!(
session_data.get_part(start_time - Duration::minutes(1)),
SessionPart::SavedFilter {
filter_id: "1".into(),
duration: 1
}
);
let start_time = Utc::now();
let session_data = StudySessionData {
definition: StudySession {
id: "session".into(),
description: "session".into(),
parts: vec![
SessionPart::SavedFilter {
filter_id: "1".into(),
duration: 1,
},
SessionPart::SavedFilter {
filter_id: "2".into(),
duration: 1,
},
SessionPart::SavedFilter {
filter_id: "3".into(),
duration: 1,
},
],
},
start_time,
};
assert_eq!(
session_data.get_part(start_time),
SessionPart::SavedFilter {
filter_id: "1".into(),
duration: 1
}
);
assert_eq!(
session_data.get_part(start_time + Duration::minutes(1)),
SessionPart::SavedFilter {
filter_id: "2".into(),
duration: 1
}
);
assert_eq!(
session_data.get_part(start_time + Duration::minutes(2)),
SessionPart::SavedFilter {
filter_id: "3".into(),
duration: 1
}
);
assert_eq!(
session_data.get_part(start_time + Duration::minutes(30)),
SessionPart::SavedFilter {
filter_id: "3".into(),
duration: 1
}
);
}
}