| 25 | 25 | | pub(crate) const PART_SQL: &str = "INSERT INTO work_item_repos (item_id, repo_id, role, added_at, added_by) |
| 26 | 26 | | VALUES (?1, ?2, 'touched', ?3, ?4) ON CONFLICT (item_id, repo_id) DO NOTHING RETURNING item_id AS value"; |
| 27 | 27 | | |
| 28 | + | /// Closes work item `?1` at `?2` once what landed for it is all it waits |
| 29 | + | /// for: open, landed somewhere, and no target left to land in (one was |
| 30 | + | /// just unlinked or made something else). One that never landed stays |
| 31 | + | /// open: it has not been done anywhere. |
| 32 | + | pub(crate) const CLOSE_IF_LANDED: &str = "UPDATE work_items SET state = 'closed', reason = 'completed', closed_at = ?2, updated_at = ?2 |
| 33 | + | WHERE id = ?1 AND state = 'open' AND resolutions <> '[]' |
| 34 | + | AND NOT EXISTS (SELECT 1 FROM work_item_repos WHERE item_id = ?1 AND role = 'target' AND landed_at IS NULL) |
| 35 | + | RETURNING id AS value"; |
| 36 | + | |
| 28 | 37 | | impl Work { |
| 29 | 38 | | /// Records which workspace `repo` is in, its path and whether it is |
| 30 | 39 | | /// private, as repos just said. |
| ⋯ |
| 154 | 163 | | .bind(&[now.as_str().into(), held.item.id.as_str().into()])? |
| 155 | 164 | | .run() |
| 156 | 165 | | .await?; |
| 166 | + | // A target no longer one: what landed may be all it waited for. |
| 167 | + | let closed = if role == LinkRole::Target && (unlink || a.role != LinkRole::Target) { |
| 168 | + | self.db |
| 169 | + | .prepare(CLOSE_IF_LANDED) |
| 170 | + | .bind(&[held.item.id.as_str().into(), now.as_str().into()])? |
| 171 | + | .first::<ValueRow>(None) |
| 172 | + | .await? |
| 173 | + | .is_some() |
| 174 | + | } else { |
| 175 | + | false |
| 176 | + | }; |
| 157 | 177 | | let Some(after) = self.item_by_id(&held.item.id).await? else { |
| 158 | 178 | | return Ok(no_item()); |
| 159 | 179 | | }; |
| ⋯ |
| 164 | 184 | | } |
| 165 | 185 | | self.publish_item(kind, &after, Some(&a.actor), WorkItemEvent { repo: Some(path), role: Some(role), ..event }).await?; |
| 166 | 186 | | self.note_item(&after.item.id, &a.actor, &text).await?; |
| 187 | + | if closed { |
| 188 | + | self.publish_item( |
| 189 | + | "work_item.closed", |
| 190 | + | &after, |
| 191 | + | Some(&a.actor), |
| 192 | + | WorkItemEvent { reason: Some(CloseReason::Completed.as_str()), missing: Some(Vec::new()), ..Self::item_event(&after) }, |
| 193 | + | ) |
| 194 | + | .await?; |
| 195 | + | self.note_item(&after.item.id, &a.actor, "closed this: it has landed in every target it has").await?; |
| 196 | + | self.item_resolved(&after, &a.actor, true, None).await; |
| 197 | + | } |
| 167 | 198 | | Ok(Outcome::Ok(after.shown(Some(&a.actor)))) |
| 168 | 199 | | } |
| 169 | 200 | | } |