hive-c0re: remove the dashboard's ask/answer surface
This commit is contained in:
parent
a295fba09c
commit
47cac50e6e
8 changed files with 25 additions and 463 deletions
|
|
@ -1,6 +1,10 @@
|
|||
//! Question queue. Agents submit via `Ask`; the answer comes from
|
||||
//! either the operator (via the dashboard, for `target IS NULL`) or
|
||||
//! a peer agent (via `Answer`, for agent-to-agent questions).
|
||||
//! either the operator (for `target IS NULL`) or a peer agent (via
|
||||
//! `Answer`, for agent-to-agent questions). ⚠️ The dashboard no longer
|
||||
//! has any UI or endpoint for the operator to actually answer a
|
||||
//! `target IS NULL` row (removed along with the rest of the dashboard's
|
||||
//! question surface) — see `questions.rs`'s module doc for the current
|
||||
//! state of that gap.
|
||||
//!
|
||||
//! Despite the file name (kept for git history sanity), this table
|
||||
//! now stores *all* asynchronous questions in the hive — both the
|
||||
|
|
@ -14,9 +18,8 @@ use std::sync::Mutex;
|
|||
|
||||
use anyhow::{Context, Result, bail};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono::Utc;
|
||||
use rusqlite::{Connection, OptionalExtension, params};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::db::Migration;
|
||||
|
||||
|
|
@ -51,36 +54,13 @@ const MIGRATIONS: &[Migration] = &[
|
|||
adds_column: Some(("operator_questions", "deadline_at")),
|
||||
},
|
||||
// v3: `target` — recipient of the question. NULL = operator (back-compat
|
||||
// default); non-null = peer-to-peer question. Dashboard's `pending()`
|
||||
// filters on `target IS NULL` so peer questions never leak to the operator.
|
||||
// default); non-null = peer-to-peer question.
|
||||
Migration {
|
||||
sql: "ALTER TABLE operator_questions ADD COLUMN target TEXT",
|
||||
adds_column: Some(("operator_questions", "target")),
|
||||
},
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct OpQuestion {
|
||||
pub id: i64,
|
||||
pub asker: String,
|
||||
pub question: String,
|
||||
pub options: Vec<String>,
|
||||
pub multi: bool,
|
||||
pub asked_at: DateTime<Utc>,
|
||||
/// Deadline after which a watchdog auto-resolves the question with
|
||||
/// answer `[expired]`. `None` = no expiry. Surfaced on the
|
||||
/// dashboard as a remaining-time chip.
|
||||
pub deadline_at: Option<DateTime<Utc>>,
|
||||
pub answered_at: Option<DateTime<Utc>>,
|
||||
pub answer: Option<String>,
|
||||
/// Recipient of the question. `None` = the operator (dashboard
|
||||
/// path); `Some(<agent>)` = a peer agent asked via
|
||||
/// `Ask { to: Some(<agent>), ... }`. Agent-to-agent questions
|
||||
/// never appear in `pending()` so the operator's queue stays clean.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub target: Option<String>,
|
||||
}
|
||||
|
||||
pub struct OperatorQuestions {
|
||||
conn: Mutex<Connection>,
|
||||
}
|
||||
|
|
@ -231,59 +211,4 @@ impl OperatorQuestions {
|
|||
)?;
|
||||
Ok((question, asker, target))
|
||||
}
|
||||
|
||||
/// Every pending question, operator-targeted or peer-to-peer.
|
||||
/// Drives the dashboard's questions pane now that peer threads
|
||||
/// are surfaced for visibility + operator override-answer.
|
||||
pub fn pending_all(&self) -> Result<Vec<OpQuestion>> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, asker, question, options_json, multi, asked_at, answered_at, answer, deadline_at, target
|
||||
FROM operator_questions
|
||||
WHERE answered_at IS NULL
|
||||
ORDER BY id ASC",
|
||||
)?;
|
||||
let rows = stmt.query_map([], row_to_question)?;
|
||||
rows.collect::<rusqlite::Result<Vec<_>>>()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Last `limit` answered questions across both target kinds,
|
||||
/// newest-first. Companion to `pending_all`.
|
||||
pub fn recent_answered_all(&self, limit: u64) -> Result<Vec<OpQuestion>> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, asker, question, options_json, multi, asked_at, answered_at, answer, deadline_at, target
|
||||
FROM operator_questions
|
||||
WHERE answered_at IS NOT NULL
|
||||
ORDER BY answered_at DESC
|
||||
LIMIT ?1",
|
||||
)?;
|
||||
let limit_i = i64::try_from(limit).unwrap_or(i64::MAX);
|
||||
let rows = stmt.query_map(params![limit_i], row_to_question)?;
|
||||
rows.collect::<rusqlite::Result<Vec<_>>>()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
fn row_to_question(row: &rusqlite::Row<'_>) -> rusqlite::Result<OpQuestion> {
|
||||
let options_json: String = row.get(3)?;
|
||||
let options: Vec<String> = serde_json::from_str(&options_json).unwrap_or_default();
|
||||
let multi: i64 = row.get(4)?;
|
||||
Ok(OpQuestion {
|
||||
id: row.get(0)?,
|
||||
asker: row.get(1)?,
|
||||
question: row.get(2)?,
|
||||
options,
|
||||
multi: multi != 0,
|
||||
asked_at: hive_sh4re::wire_time::from_secs(row.get(5)?),
|
||||
answered_at: row
|
||||
.get::<_, Option<i64>>(6)?
|
||||
.map(hive_sh4re::wire_time::from_secs),
|
||||
answer: row.get(7)?,
|
||||
deadline_at: row
|
||||
.get::<_, Option<i64>>(8)?
|
||||
.map(hive_sh4re::wire_time::from_secs),
|
||||
target: row.get(9)?,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue