mod notice;
mod stats;
pub use notice::*;
pub use stats::*;
use super::*;
use chrono::prelude::{Local, NaiveDate, NaiveTime};
use mkutil::datetime::parse_naive_date;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ReviewAction {
Pilot(Project, AssetExcerpt),
MarkReviewed {
project: Project,
asset: AssetExcerpt,
typ: Supervision,
is_next_to_last: bool,
window_id: Option<String>,
},
MarkNotReviewed(Project, AssetExcerpt, Supervision),
DebugNoticeSequence(Project, AssetExcerpt),
}
impl ReviewAction {
fn pilot(project: &Project, asset: &AssetExcerpt) -> Self {
Self::Pilot(project.clone(), asset.clone())
}
fn mark_reviewed(
project: &Project,
asset: &AssetExcerpt,
typ: &Supervision,
existing_unreviewed: &usize,
window_id: &String,
) -> Self {
let is_next_to_last = *existing_unreviewed == 1;
Self::MarkReviewed {
project: project.clone(),
asset: asset.clone(),
typ: typ.clone(),
window_id: if is_next_to_last {
Some(window_id.to_owned())
} else {
None
},
is_next_to_last,
}
}
fn mark_not_reviewed(project: &Project, asset: &AssetExcerpt, typ: &Supervision) -> Self {
Self::MarkNotReviewed(project.clone(), asset.clone(), typ.clone())
}
#[cfg(debug_assertions)]
fn debug_notice_sequence(project: &Project, asset: &AssetExcerpt) -> Self {
Self::DebugNoticeSequence(project.clone(), asset.clone())
}
}
pub type ProjReviewVerdict = HashMap<AssetExcerpt, DailyReview>;
#[derive(Debug, Clone, strum::AsRefStr, strum::EnumIter, PartialEq, Eq, Hash)]
pub enum Supervision {
#[strum(serialize = "wip_review")]
Wip,
#[strum(serialize = "feedback_review")]
Feedback,
#[strum(serialize = "approval_review")]
Approval,
#[strum(serialize = "client_feedback")]
Client,
}
impl Supervision {
pub fn ad_column_header(&self) -> &str {
match self {
Self::Wip => "WIP | Daily",
Self::Feedback => "Feedback Complete",
Self::Approval => "Submission for Review",
Self::Client => "You Have Feedback",
}
}
fn signaling_label(&self) -> &str {
match self {
Self::Wip => "🗠 WIP | Daily",
Self::Feedback => "",
Self::Approval => "🙏 For Approval",
Self::Client => "🚔 Client Feedback",
}
}
fn draft_tooltip(&self) -> &str {
match self {
Self::Wip => "Enable this review item after you submit\nWIP|Daily snapshots",
Self::Feedback => "",
Self::Approval => {
"Enable this review item after you submit\nFor Approval snapshots, or UnitedTT movie"
}
Self::Client => "Enable this review item to add one count\nfor this asset in PanProject Review table stats",
}
}
}
#[async_trait]
pub trait ReviewStats: DynClone + fmt::Debug + Send + Sync {
async fn review_item(
&mut self,
project: &Project,
asset: &ProductionAsset,
typ: &Supervision,
date: &DateTime<Local>,
) -> Result<Notice, DatabaseError>;
async fn submit_notice(
&mut self,
project: &Project,
asset: &ProductionAsset,
notice: Notice,
submit_time: &DateTime<Local>,
) -> Result<(), ModificationError>;
async fn mark_viewed(
&mut self,
project: &Project,
asset: &ProductionAsset,
typ: &Supervision,
submit_time: &DateTime<Local>,
reviewer: &ProductionRole,
target: bool,
) -> Result<(), ModificationError>;
async fn asset_notice_sequence(
&mut self,
project: &Project,
asset: &ProductionAsset,
trim_options: &TrimOptions,
) -> Result<NoticeSequence, DatabaseError>;
async fn asset_review_verdict(
&mut self,
project: &Project,
asset: &ProductionAsset,
trim_options: &TrimOptions,
reviewer: &ProductionRole,
) -> Result<DailyReview, DatabaseError>;
async fn project_review_stats(
&mut self,
project: &Project,
trim_options: &TrimOptions,
reviewer: &ProductionRole,
) -> Result<ProjReviewStats, DatabaseError>;
async fn panproject_review_stats(
&mut self,
pp_members: &[Project],
trim_options: &TrimOptions,
reviewer: &ProductionRole,
) -> Result<PanProjectReviewStats, DatabaseError>;
fn clear_cache(&mut self) {}
}
dyn_clone::clone_trait_object!(ReviewStats);
type ReviewSequence = Vec<(NaiveDate, DailyReview)>;
#[derive(Debug, Clone)]
pub struct ReviewVerdictBuilder(ReviewSequence);
impl ReviewVerdictBuilder {
pub fn new(seq: &NoticeSequence, reviewer_role: &ProductionRole) -> Self {
let mut sequence = vec![];
for (date, v) in seq.sequence_date.iter() {
if let Some(notice) = v {
sequence.push((date.clone(), notice.is_reviewed_by(reviewer_role)));
};
}
Self(
sequence.into_iter().rev().collect(),
)
}
pub fn build(self) -> DailyReview {
DailyReview {
wip: Reviewed::verdict(self.0.iter().map(|r| &r.1.wip)),
feedback: Reviewed::verdict(self.0.iter().map(|r| &r.1.feedback)),
approval: Reviewed::verdict(self.0.iter().map(|r| &r.1.approval)),
client: Reviewed::verdict(self.0.iter().map(|r| &r.1.client)),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NoticeSequence {
#[serde(rename = "daily_review")]
pub sequence_str: Option<HashMap<String, DailyNotice>>,
#[serde(skip)]
pub sequence_date: Vec<(NaiveDate, Option<DailyNotice>)>,
}
impl NoticeSequence {
pub fn filter_range(self) -> Vec<(NaiveDate, DailyNotice)> {
self.sequence_date
.into_iter()
.filter_map(|(date, notice)| match notice {
Some(notice) => Some((date, notice)),
None => None,
})
.collect()
}
pub fn debug_range(self) -> String {
let mut seq = String::new();
self.filter_range().iter().for_each(|(date, notice)| {
seq.push_str(&format!("\n---- {} ----", date));
seq.push_str(&format!("\n{} {:?}", Supervision::Wip.as_ref(), notice.wip));
seq.push_str(&format!(
"\n{} {:?}",
Supervision::Feedback.as_ref(),
notice.feedback
));
seq.push_str(&format!(
"\n{} {:?}",
Supervision::Approval.as_ref(),
notice.approval
));
seq.push_str(&format!(
"\n{} {:?}",
Supervision::Client.as_ref(),
notice.client
));
});
seq
}
fn trim(&mut self, trim_options: &TrimOptions) {
let trim_options: NaiveTrimOptions = trim_options.into();
for (k, v) in self.sequence_date.iter_mut() {
if let Some(notice) = v {
notice.trim(k, &trim_options);
if notice.is_empty() {
v.take();
};
};
}
}
fn trim_naive(&mut self, trim_options: &NaiveTrimOptions) {
for (k, v) in self.sequence_date.iter_mut() {
if let Some(notice) = v {
notice.trim(k, &trim_options);
if notice.is_empty() {
v.take();
};
};
}
}
}
#[derive(Debug)]
pub struct NoticeSequenceReadBuilder(NoticeSequence);
impl NoticeSequenceReadBuilder {
pub fn new(seq: NoticeSequence) -> Self {
Self(seq)
}
fn trim(mut self, trim_options: &TrimOptions) -> Self {
self.0.trim(trim_options);
self
}
fn trim_naive(mut self, trim_options: &NaiveTrimOptions) -> Self {
self.0.trim_naive(trim_options);
self
}
fn parse_submit_time(mut self, fmt: &str) -> Self {
if let Some(sequence) = self.0.sequence_str.as_mut() {
sequence.values_mut().for_each(|d| d.parse_submit_time(fmt));
};
self
}
fn parse_review_date(mut self, fmt: &str) -> Self {
if let Some(sequence) = self.0.sequence_str.take() {
let mut seq_date = vec![];
for (k, v) in sequence.into_iter() {
match parse_naive_date(&k, fmt) {
Ok(date) => {
seq_date.push((date, Some(v)));
}
Err(e) => {
error!("Skipping DailyNotice of {:?} due to: {}", k, e);
}
}
}
seq_date.sort_by_key(|s| s.0.to_owned());
self.0.sequence_date = seq_date;
};
self
}
pub fn finish(
self,
time_format: &str,
date_format: &str,
trim_options: &TrimOptions,
) -> NoticeSequence {
self.parse_submit_time(time_format)
.parse_review_date(date_format)
.trim(trim_options)
.0
}
pub fn finish_reuse(
self,
date_format: &str,
trim_options: &NaiveTrimOptions,
) -> NoticeSequence {
self.parse_review_date(date_format)
.trim_naive(trim_options)
.0
}
}
pub fn submit_date_str(date: &DateTime<Local>) -> AnyResult<String> {
Ok(format!("{}", date.format(date_fmt_cfg()?)))
}
fn submit_time_str(time: &DateTime<Local>) -> AnyResult<String> {
Ok(format!("{}", time.format(time_fmt_cfg()?)))
}
pub fn time_fmt_cfg() -> AnyResult<&'static String> {
let settings = ClientCfgCel::settings().as_ref()?;
Ok(settings.notice_submit_time_format())
}
pub fn date_fmt_cfg() -> AnyResult<&'static String> {
let settings = ClientCfgCel::settings().as_ref()?;
Ok(settings.notice_submit_day_format())
}