Merge pull request #330 from djmitche/issue327

Support an 'end' key in task maps
This commit is contained in:
Dustin J. Mitchell 2022-01-24 10:24:18 -05:00 committed by GitHub
commit 8576e7ffa7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 93 additions and 4 deletions

View file

@ -17,6 +17,12 @@ The result of this reconciliation will be `oldtag,newtag2`, while the user almos
The key names given below avoid this issue, allowing user updates such as adding a tag or deleting a dependency to be represented in a single `Update` operation.
## Validity
_Any_ key/value map is a valid task.
Consumers of task data must make a best effort to interpret any map, even if it contains apparently contradictory information.
For example, a task with status "completed" but no "end" key present should be interpreted as completed at an unknown time.
## Representations
Integers are stored in decimal notation.
@ -31,6 +37,7 @@ The following keys, and key formats, are defined:
* `description` - the one-line summary of the task
* `modified` - the time of the last modification of this task
* `start` - the most recent time at which this task was started (a task with no `start` key is not active)
* `end` - if present, the time at which this task was completed or deleted (note that this key may not agree with `status`: it may be present for a pending task, or absent for a deleted or completed task)
* `tag_<tag>` - indicates this task has tag `<tag>` (value is an empty string)
* `wait` - indicates the time before which this task should be hidden, as it is not actionable
* `entry` - the time at which the task was created

View file

@ -241,7 +241,9 @@ mod tests {
..
} = op
{
if property == "modified" || property == "entry" {
// rewrite automatically-created dates to "just-now" for ease
// of testing
if property == "modified" || property == "end" || property == "entry" {
if value.is_some() {
value = Some("just-now".into());
}
@ -311,6 +313,13 @@ mod tests {
value: Some("past tense".into()),
timestamp: now,
},
ReplicaOp::Update {
uuid: t.get_uuid(),
property: "end".into(),
old_value: None,
value: Some("just-now".into()),
timestamp: now,
},
ReplicaOp::Update {
uuid: t.get_uuid(),
property: "status".into(),

View file

@ -57,6 +57,7 @@ enum Prop {
Start,
Status,
Wait,
End,
Entry,
}
@ -271,10 +272,23 @@ impl<'r> TaskMut<'r> {
/// Set the task's status. This also adds the task to the working set if the
/// new status puts it in that set.
pub fn set_status(&mut self, status: Status) -> anyhow::Result<()> {
if status == Status::Pending {
match status {
Status::Pending => {
// clear "end" when a task becomes "pending"
if self.taskmap.contains_key(Prop::End.as_ref()) {
self.set_timestamp(Prop::End.as_ref(), None)?;
}
let uuid = self.uuid;
self.replica.add_to_working_set(uuid)?;
}
Status::Completed | Status::Deleted => {
// set "end" when a task is deleted or completed
if !self.taskmap.contains_key(Prop::End.as_ref()) {
self.set_timestamp(Prop::End.as_ref(), Some(Utc::now()))?;
}
}
_ => {}
}
self.set_string(
Prop::Status.as_ref(),
Some(String::from(status.to_taskmap())),
@ -316,6 +330,14 @@ impl<'r> TaskMut<'r> {
self.set_status(Status::Completed)
}
/// Mark this task as deleted.
///
/// Note that this does not delete the task. It merely marks the task as
/// deleted.
pub fn delete(&mut self) -> anyhow::Result<()> {
self.set_status(Status::Deleted)
}
/// Add a tag to this task. Does nothing if the tag is already present.
pub fn add_tag(&mut self, tag: &Tag) -> anyhow::Result<()> {
if tag.is_synthetic() {
@ -688,6 +710,41 @@ mod test {
});
}
#[test]
fn test_set_status_pending() {
with_mut_task(|mut task| {
task.done().unwrap();
task.set_status(Status::Pending).unwrap();
assert_eq!(task.get_status(), Status::Pending);
assert!(!task.taskmap.contains_key("end"));
assert!(task.has_tag(&stag(SyntheticTag::Pending)));
assert!(!task.has_tag(&stag(SyntheticTag::Completed)));
});
}
#[test]
fn test_set_status_completed() {
with_mut_task(|mut task| {
task.set_status(Status::Completed).unwrap();
assert_eq!(task.get_status(), Status::Completed);
assert!(task.taskmap.contains_key("end"));
assert!(!task.has_tag(&stag(SyntheticTag::Pending)));
assert!(task.has_tag(&stag(SyntheticTag::Completed)));
});
}
#[test]
fn test_set_status_deleted() {
with_mut_task(|mut task| {
task.set_status(Status::Deleted).unwrap();
assert_eq!(task.get_status(), Status::Deleted);
assert!(task.taskmap.contains_key("end"));
assert!(!task.has_tag(&stag(SyntheticTag::Pending)));
assert!(!task.has_tag(&stag(SyntheticTag::Completed)));
});
}
#[test]
fn test_start() {
with_mut_task(|mut task| {
@ -730,6 +787,7 @@ mod test {
with_mut_task(|mut task| {
task.done().unwrap();
assert_eq!(task.get_status(), Status::Completed);
assert!(task.taskmap.contains_key("end"));
assert!(task.has_tag(&stag(SyntheticTag::Completed)));
// redundant call does nothing..
@ -739,6 +797,21 @@ mod test {
});
}
#[test]
fn test_delete() {
with_mut_task(|mut task| {
task.delete().unwrap();
assert_eq!(task.get_status(), Status::Deleted);
assert!(task.taskmap.contains_key("end"));
assert!(!task.has_tag(&stag(SyntheticTag::Completed)));
// redundant call does nothing..
task.delete().unwrap();
assert_eq!(task.get_status(), Status::Deleted);
assert!(!task.has_tag(&stag(SyntheticTag::Completed)));
});
}
#[test]
fn test_add_tags() {
with_mut_task(|mut task| {