use super::RevisionProjectionSummary;
use crate::model::{
AssessmentId, DiffFile, DiffSnapshot, InputRequestId, ObservationId, ReviewTargetRef,
RevisionId, RowId, ValidationCheckId, ValidationTarget, id_prefix,
};
use crate::session::assessment::AssessmentView;
use crate::session::input_request::InputRequestView;
use crate::session::observation::ObservationView;
use crate::session::workflow::ValidationCheckView;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RevisionProjectionRow {
pub id: RowId,
pub kind: RevisionProjectionRowKind,
pub projection_phase: ProjectionPhase,
pub projection_order: usize,
pub snapshot_order: Option<SnapshotOrder>,
pub coverage: ProjectionCoverage,
pub target: Option<ReviewTargetRef>,
pub file_path: Option<String>,
pub old_path: Option<String>,
pub related_observation_ids: Vec<ObservationId>,
pub related_input_request_ids: Vec<InputRequestId>,
pub related_assessment_ids: Vec<AssessmentId>,
pub related_validation_check_ids: Vec<ValidationCheckId>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RevisionProjectionRowKind {
FileHeader,
Metadata,
HunkHeader,
Diff,
Observation,
InputRequest,
Assessment,
ValidationEvidence,
EmptyState,
}
impl RevisionProjectionRowKind {
pub fn as_str(self) -> &'static str {
match self {
Self::FileHeader => "file_header",
Self::Metadata => "metadata",
Self::HunkHeader => "hunk_header",
Self::Diff => "diff",
Self::Observation => "observation",
Self::InputRequest => "input_request",
Self::Assessment => "assessment",
Self::ValidationEvidence => "validation_evidence",
Self::EmptyState => "empty_state",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProjectionPhase {
Narrative,
SnapshotRemainder,
}
impl ProjectionPhase {
pub fn as_str(self) -> &'static str {
match self {
Self::Narrative => "narrative",
Self::SnapshotRemainder => "snapshot_remainder",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProjectionCoverage {
Context,
Reviewed,
Unreviewed,
}
impl ProjectionCoverage {
pub fn as_str(self) -> &'static str {
match self {
Self::Context => "context",
Self::Reviewed => "reviewed",
Self::Unreviewed => "unreviewed",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SnapshotOrder {
pub file_index: usize,
pub metadata_index: Option<usize>,
pub hunk_index: Option<usize>,
pub row_index: Option<usize>,
}
pub(super) fn build_snapshot_rows(
snapshot: &DiffSnapshot,
revision_id: &RevisionId,
) -> (Vec<RevisionProjectionRow>, RevisionProjectionSummary) {
let mut rows = Vec::new();
if snapshot.files.is_empty() {
rows.push(snapshot_row(
rows.len(),
RevisionProjectionRowKind::EmptyState,
None,
ProjectionCoverage::Context,
None,
None,
None,
));
}
for (file_index, file) in snapshot.files.iter().enumerate() {
let file_path = snapshot_file_path(file);
let old_path = file.old_path.clone();
let file_target = file_path.as_ref().map(|file_path| ReviewTargetRef::File {
revision_id: revision_id.clone(),
file_path: file_path.clone(),
});
rows.push(snapshot_row(
rows.len(),
RevisionProjectionRowKind::FileHeader,
Some(SnapshotOrder {
file_index,
metadata_index: None,
hunk_index: None,
row_index: None,
}),
ProjectionCoverage::Unreviewed,
file_target,
file_path.clone(),
old_path.clone(),
));
for (metadata_index, _) in file.metadata_rows.iter().enumerate() {
rows.push(snapshot_row(
rows.len(),
RevisionProjectionRowKind::Metadata,
Some(SnapshotOrder {
file_index,
metadata_index: Some(metadata_index),
hunk_index: None,
row_index: None,
}),
ProjectionCoverage::Unreviewed,
None,
file_path.clone(),
old_path.clone(),
));
}
for (hunk_index, hunk) in file.hunks.iter().enumerate() {
rows.push(snapshot_row(
rows.len(),
RevisionProjectionRowKind::HunkHeader,
Some(SnapshotOrder {
file_index,
metadata_index: None,
hunk_index: Some(hunk_index),
row_index: None,
}),
ProjectionCoverage::Unreviewed,
None,
file_path.clone(),
old_path.clone(),
));
for (row_index, _) in hunk.rows.iter().enumerate() {
rows.push(snapshot_row(
rows.len(),
RevisionProjectionRowKind::Diff,
Some(SnapshotOrder {
file_index,
metadata_index: None,
hunk_index: Some(hunk_index),
row_index: Some(row_index),
}),
ProjectionCoverage::Unreviewed,
None,
file_path.clone(),
old_path.clone(),
));
}
}
}
let summary = RevisionProjectionSummary {
file_count: snapshot.files.len(),
row_count: rows.len(),
snapshot_row_count: rows.len(),
snapshot_remainder_row_count: rows.len(),
..RevisionProjectionSummary::default()
};
(rows, summary)
}
pub(super) fn build_observation_rows(
observations: &[ObservationView],
) -> Vec<RevisionProjectionRow> {
observations
.iter()
.enumerate()
.map(|(index, observation)| {
let (file_path, old_path) = target_paths(&observation.target);
RevisionProjectionRow {
id: RowId::new(format!("{}:{index:06}", id_prefix::ROW)),
kind: RevisionProjectionRowKind::Observation,
projection_phase: ProjectionPhase::Narrative,
projection_order: index,
snapshot_order: None,
coverage: ProjectionCoverage::Reviewed,
target: Some(observation.target.clone()),
file_path,
old_path,
related_observation_ids: vec![observation.id.clone()],
related_input_request_ids: Vec::new(),
related_assessment_ids: Vec::new(),
related_validation_check_ids: Vec::new(),
}
})
.collect()
}
pub(super) fn build_input_request_rows(
input_requests: &[InputRequestView],
) -> Vec<RevisionProjectionRow> {
input_requests
.iter()
.enumerate()
.map(|(index, input_request)| {
let (file_path, old_path) = target_paths(&input_request.target);
RevisionProjectionRow {
id: RowId::new(format!("{}:{index:06}", id_prefix::ROW)),
kind: RevisionProjectionRowKind::InputRequest,
projection_phase: ProjectionPhase::Narrative,
projection_order: index,
snapshot_order: None,
coverage: ProjectionCoverage::Reviewed,
target: Some(input_request.target.clone()),
file_path,
old_path,
related_observation_ids: Vec::new(),
related_input_request_ids: vec![input_request.id.clone()],
related_assessment_ids: Vec::new(),
related_validation_check_ids: Vec::new(),
}
})
.collect()
}
pub(super) fn build_assessment_rows(assessments: &[AssessmentView]) -> Vec<RevisionProjectionRow> {
assessments
.iter()
.enumerate()
.map(|(index, assessment)| {
let (file_path, old_path) = target_paths(&assessment.target);
RevisionProjectionRow {
id: RowId::new(format!("{}:{index:06}", id_prefix::ROW)),
kind: RevisionProjectionRowKind::Assessment,
projection_phase: ProjectionPhase::Narrative,
projection_order: index,
snapshot_order: None,
coverage: ProjectionCoverage::Reviewed,
target: Some(assessment.target.clone()),
file_path,
old_path,
related_observation_ids: assessment.related_observations.clone(),
related_input_request_ids: assessment.related_input_requests.clone(),
related_assessment_ids: vec![assessment.id.clone()],
related_validation_check_ids: Vec::new(),
}
})
.collect()
}
pub(super) fn build_validation_rows(
validations: &[ValidationCheckView],
) -> Vec<RevisionProjectionRow> {
validations
.iter()
.enumerate()
.map(|(index, validation)| {
let target = validation_target_to_review_target(&validation.target);
let (file_path, old_path) = target_paths(&target);
RevisionProjectionRow {
id: RowId::new(format!("{}:{index:06}", id_prefix::ROW)),
kind: RevisionProjectionRowKind::ValidationEvidence,
projection_phase: ProjectionPhase::Narrative,
projection_order: index,
snapshot_order: None,
coverage: ProjectionCoverage::Reviewed,
target: Some(target),
file_path,
old_path,
related_observation_ids: Vec::new(),
related_input_request_ids: Vec::new(),
related_assessment_ids: Vec::new(),
related_validation_check_ids: vec![validation.id.clone()],
}
})
.collect()
}
pub(super) fn snapshot_row(
projection_order: usize,
kind: RevisionProjectionRowKind,
snapshot_order: Option<SnapshotOrder>,
coverage: ProjectionCoverage,
target: Option<ReviewTargetRef>,
file_path: Option<String>,
old_path: Option<String>,
) -> RevisionProjectionRow {
RevisionProjectionRow {
id: RowId::new(format!("{}:{projection_order:06}", id_prefix::ROW)),
kind,
projection_phase: ProjectionPhase::SnapshotRemainder,
projection_order,
snapshot_order,
coverage,
target,
file_path,
old_path,
related_observation_ids: Vec::new(),
related_input_request_ids: Vec::new(),
related_assessment_ids: Vec::new(),
related_validation_check_ids: Vec::new(),
}
}
pub(super) fn renumber_projection_rows(rows: &mut [RevisionProjectionRow]) {
for (index, row) in rows.iter_mut().enumerate() {
row.id = RowId::new(format!("{}:{index:06}", id_prefix::ROW));
row.projection_order = index;
}
}
pub(super) fn snapshot_file_path(file: &DiffFile) -> Option<String> {
file.new_path.clone().or_else(|| file.old_path.clone())
}
pub(super) fn target_paths(target: &ReviewTargetRef) -> (Option<String>, Option<String>) {
match target {
ReviewTargetRef::File { file_path, .. } | ReviewTargetRef::Range { file_path, .. } => {
(Some(file_path.clone()), None)
}
ReviewTargetRef::Revision { .. }
| ReviewTargetRef::Observation { .. }
| ReviewTargetRef::InputRequest { .. }
| ReviewTargetRef::Assessment { .. }
| ReviewTargetRef::Event { .. } => (None, None),
}
}
fn validation_target_to_review_target(target: &ValidationTarget) -> ReviewTargetRef {
match target {
ValidationTarget::Revision { revision_id } => ReviewTargetRef::Revision {
revision_id: revision_id.clone(),
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn revision_projection_row_kind_validation_evidence_wire_string() {
assert_eq!(
RevisionProjectionRowKind::ValidationEvidence.as_str(),
"validation_evidence"
);
}
}