Răsfoiți Sursa

tau: add bounty column

dasman 9 luni în urmă
părinte
comite
27047c6538

+ 25 - 4
bin/tau/tau-python/tau

@@ -8,7 +8,7 @@ from colorama import Fore, Style
 
 import api, lib.util
 
-known_attrs = ["desc", "rank", "due", "project"]
+known_attrs = ["desc", "rank", "due", "project", "bounty"]
 
 async def add_task(task_args, server_name, port):
     task = {
@@ -19,6 +19,7 @@ async def add_task(task_args, server_name, port):
         "project": [],
         "due": None,
         "rank": None,
+        "bounty": None,
         "created_at": lib.util.now(),
         "state": "open"
     }
@@ -58,6 +59,9 @@ async def add_task(task_args, server_name, port):
     
     if task["rank"] is not None:
         task["rank"] = round(task["rank"], 4)
+
+    if task["bounty"] is not None:
+        task["bounty"] = round(task["bounty"], 4)
     
     try:
         if task["ref_id"].strip() == '':
@@ -145,6 +149,13 @@ def convert_attr_val(attr, val):
             print(f"error: rank value {val} isn't convertable to float",
                   file=sys.stderr)
             sys.exit(-1)
+    elif attr == "bounty":
+        try:
+            return float(val)
+        except ValueError:
+            print(f"error: bounty value {val} isn't convertable to float",
+                  file=sys.stderr)
+            sys.exit(-1)
     elif attr == "due":
         # Other date formats not yet supported... ez to add
         if len(val) != 4:
@@ -229,7 +240,8 @@ async def show_log(server_name, port, timeframe):
 def list_tasks(tasks, workspace, filters):
     print(f"Workspace: {workspace}")
     headers = ["ID", "Title", "Status", "Project",
-               "Tags", "assign", "Rank", "Due", "RefID"]
+               "Tags", "assign", "Rank", "Due", 
+               "Bounty", "RefID"]
     table_rows = []
     for id, task in enumerate(tasks, 1):
         if task is None:
@@ -251,6 +263,8 @@ def list_tasks(tasks, workspace, filters):
 
         rank = round(task["rank"], 4) if task["rank"] is not None else ""
 
+        bounty = "$" + str(round(task["bounty"], 4)) if task["bounty"] is not None else ""
+
         if status == "start":
             id =        Fore.GREEN + str(id)         + Style.RESET_ALL
             title =     Fore.GREEN + str(title)      + Style.RESET_ALL
@@ -260,6 +274,7 @@ def list_tasks(tasks, workspace, filters):
             assign =    Fore.GREEN + str(assign)     + Style.RESET_ALL
             rank =      Fore.GREEN + str(rank)       + Style.RESET_ALL
             due =       Fore.GREEN + str(due)        + Style.RESET_ALL
+            bounty =    Fore.GREEN + str(bounty)     + Style.RESET_ALL
             ref_id =    Fore.GREEN + str(ref_id)     + Style.RESET_ALL
         elif status == "pause":
             id =        Fore.YELLOW + str(id)        + Style.RESET_ALL
@@ -270,6 +285,7 @@ def list_tasks(tasks, workspace, filters):
             assign =    Fore.YELLOW + str(assign)    + Style.RESET_ALL
             rank =      Fore.YELLOW + str(rank)      + Style.RESET_ALL
             due =       Fore.YELLOW + str(due)       + Style.RESET_ALL
+            bounty =    Fore.YELLOW + str(bounty)    + Style.RESET_ALL
             ref_id =    Fore.YELLOW + str(ref_id)    + Style.RESET_ALL
         elif status == "stop":
             id =        Fore.RED + str(id)           + Style.RESET_ALL
@@ -280,6 +296,7 @@ def list_tasks(tasks, workspace, filters):
             assign =    Fore.RED + str(assign)       + Style.RESET_ALL
             rank =      Fore.RED + str(rank)         + Style.RESET_ALL
             due =       Fore.RED + str(due)          + Style.RESET_ALL
+            bounty =    Fore.RED + str(bounty)       + Style.RESET_ALL
             ref_id =    Fore.RED + str(ref_id)       + Style.RESET_ALL
         else:
             #id =       Style.DIM  + str(id)         + Style.RESET_ALL
@@ -290,6 +307,7 @@ def list_tasks(tasks, workspace, filters):
             #assign =   Style.DIM  + str(assign)     + Style.RESET_ALL
             rank =      Style.DIM  + str(rank)       + Style.RESET_ALL
             due =       Style.DIM  + str(due)        + Style.RESET_ALL
+            bounty =    Style.DIM  + str(bounty)     + Style.RESET_ALL
             #ref_id =   Style.DIM  + str(ref_id)     + Style.RESET_ALL
 
         rank_value = task["rank"] if task["rank"] is not None else 0
@@ -302,6 +320,7 @@ def list_tasks(tasks, workspace, filters):
             assign,
             rank,
             due,
+            bounty,
             ref_id
         ]
         table_rows.append((rank_value, row))
@@ -325,6 +344,7 @@ def tabulate_task(task, prompt):
     assign = " ".join(f"{assign}" for assign in task["assign"])
     project = " ".join(f"{project}" for project in task["project"])
     rank = round(task["rank"], 4) if task["rank"] is not None else ""
+    bounty = round(task["bounty"], 4) if task["bounty"] is not None else ""
     if task["due"] is None:
         due = ""
     else:
@@ -350,6 +370,7 @@ def tabulate_task(task, prompt):
         ["Assign:", assign],
         ["Rank:", rank],
         ["Due:", due],
+        ["Bounty:", bounty],
         ["Created:", created_at],
     ]
     return tabulate(table, headers=["Attribute", "Value"])
@@ -460,7 +481,7 @@ async def modify_task(refid, args, server_name, port):
         elif ":" in arg:
             attr, val = arg.split(":", 1)
             if val.lower() == "none":
-                if attr not in ["project", "rank", "due"]:
+                if attr not in ["project", "rank", "due", "bounty"]:
                     print(f"error: invalid you cannot set {attr} to none",
                           file=sys.stderr)
                     return -1
@@ -529,7 +550,7 @@ def is_filtered(task, filters):
         elif ":" in fltr:
             attr, val = fltr.split(":", 1)
             if val.lower() == "none":
-                if attr not in ["project", "rank", "due"]:
+                if attr not in ["project", "rank", "due", "bounty"]:
                     print(f"error: invalid you cannot set {attr} to none",
                             file=sys.stderr)
                     sys.exit(-1)

+ 23 - 6
bin/tau/taud/src/jsonrpc.rs

@@ -17,7 +17,7 @@
  */
 
 use std::{
-    collections::{HashMap, HashSet},
+    collections::{BTreeMap, HashMap, HashSet},
     fs::create_dir_all,
     path::PathBuf,
     sync::Arc,
@@ -50,14 +50,12 @@ use taud::{
 
 use crate::Workspace;
 
-const DEFAULT_WORKSPACE: &str = "darkfi-dev";
-
 pub struct JsonRpcInterface {
     dataset_path: PathBuf,
     notify_queue_sender: smol::channel::Sender<TaskInfo>,
     nickname: String,
     workspace: Mutex<String>,
-    workspaces: Arc<HashMap<String, Workspace>>,
+    workspaces: Arc<BTreeMap<String, Workspace>>,
     p2p: net::P2pPtr,
     event_graph: EventGraphPtr,
     dnet_sub: JsonSubscriber,
@@ -115,13 +113,14 @@ impl JsonRpcInterface {
         dataset_path: PathBuf,
         notify_queue_sender: smol::channel::Sender<TaskInfo>,
         nickname: String,
-        workspaces: Arc<HashMap<String, Workspace>>,
+        workspace: String,
+        workspaces: Arc<BTreeMap<String, Workspace>>,
         p2p: net::P2pPtr,
         event_graph: EventGraphPtr,
         dnet_sub: JsonSubscriber,
         deg_sub: JsonSubscriber,
     ) -> Self {
-        let workspace = Mutex::new(DEFAULT_WORKSPACE.to_string());
+        let workspace = Mutex::new(workspace);
         Self {
             dataset_path,
             nickname,
@@ -271,6 +270,12 @@ impl JsonRpcInterface {
             _ => return Err(TaudError::InvalidData("Invalid parameter \"rank\"".to_string())),
         };
 
+        let bounty = match params["bounty"] {
+            JsonValue::Null => None,
+            JsonValue::Number(numba) => Some(numba as f32),
+            _ => return Err(TaudError::InvalidData("Invalid parameter \"bounty\"".to_string())),
+        };
+
         let tags = {
             let mut tags = vec![];
 
@@ -332,6 +337,7 @@ impl JsonRpcInterface {
             due,
             rank,
             Timestamp::from_u64(created_at.unwrap()),
+            bounty,
         )?;
         new_task.set_project(&projects);
         new_task.set_assign(&assigns);
@@ -717,6 +723,17 @@ impl JsonRpcInterface {
             }
         }
 
+        if fields.contains_key("bounty") {
+            match fields["bounty"] {
+                JsonValue::Null => set_event(&mut task, "bounty", &self.nickname, "None"),
+                JsonValue::Number(bounty) => {
+                    task.set_bounty(Some(bounty as f32));
+                    set_event(&mut task, "bounty", &self.nickname, &bounty.to_string())
+                }
+                _ => unreachable!(),
+            }
+        }
+
         if fields.contains_key("due") {
             match &fields["due"] {
                 JsonValue::Null => set_event(&mut task, "due", &self.nickname, "None"),

+ 8 - 6
bin/tau/taud/src/main.rs

@@ -17,7 +17,7 @@
  */
 
 use std::{
-    collections::HashMap,
+    collections::BTreeMap,
     env,
     ffi::CString,
     fs::{create_dir_all, remove_dir_all},
@@ -165,8 +165,8 @@ fn try_decrypt_task(
     Ok(signed_task)
 }
 
-fn parse_configured_workspaces(data: &toml::Value) -> Result<HashMap<String, Workspace>> {
-    let mut ret = HashMap::new();
+fn parse_configured_workspaces(data: &toml::Value) -> Result<BTreeMap<String, Workspace>> {
+    let mut ret = BTreeMap::new();
 
     let Some(table) = data.as_table() else { return Err(Error::ParseFailed("TOML not a map")) };
     let Some(workspace) = table.get("workspace") else { return Ok(ret) };
@@ -251,7 +251,7 @@ fn parse_configured_workspaces(data: &toml::Value) -> Result<HashMap<String, Wor
     Ok(ret)
 }
 
-async fn get_workspaces(settings: &Args) -> Result<HashMap<String, Workspace>> {
+async fn get_workspaces(settings: &Args) -> Result<BTreeMap<String, Workspace>> {
     let config_path = get_config_path(settings.config.clone(), CONFIG_FILE)?;
     let contents = fs::read_to_string(config_path).await?;
     let contents = match toml::from_str(&contents) {
@@ -296,7 +296,7 @@ pub async fn is_seen(
 async fn start_sync_loop(
     event_graph: EventGraphPtr,
     broadcast_rcv: smol::channel::Receiver<TaskInfo>,
-    workspaces: Arc<HashMap<String, Workspace>>,
+    workspaces: Arc<BTreeMap<String, Workspace>>,
     sled_db: sled::Db,
     settings: Args,
     p2p: P2pPtr,
@@ -362,7 +362,7 @@ async fn start_sync_loop(
 /// to a named pipe and save it on disk.
 async fn on_receive_task(
     enc_task: &EncryptedTask,
-    workspaces: &HashMap<String, Workspace>,
+    workspaces: &BTreeMap<String, Workspace>,
     settings: &Args,
 ) -> TaudResult<()> {
     for (ws_name, workspace) in workspaces.iter() {
@@ -505,6 +505,7 @@ async fn realmain(settings: Args, executor: Arc<smol::Executor<'static>>) -> Res
     }
 
     let workspaces = Arc::new(get_workspaces(&settings).await?);
+    let (workspace, _) = workspaces.first_key_value().unwrap();
     // let verified = Arc::new(Mutex::new(false));
 
     if workspaces.is_empty() {
@@ -691,6 +692,7 @@ async fn realmain(settings: Args, executor: Arc<smol::Executor<'static>>) -> Res
         datastore_path.clone(),
         broadcast_snd,
         nickname.unwrap(),
+        workspace.to_string(),
         workspaces.clone(),
         p2p.clone(),
         event_graph.clone(),

+ 2 - 0
bin/tau/taud/src/month_tasks.rs

@@ -271,6 +271,7 @@ mod tests {
             None,
             Some(0.0),
             Timestamp::current_time(),
+            None,
         )?;
 
         task.save(&dataset_path)?;
@@ -319,6 +320,7 @@ mod tests {
             None,
             Some(0.0),
             Timestamp::current_time(),
+            None,
         )?;
 
         task.save(&dataset_path)?;

+ 24 - 0
bin/tau/taud/src/task_info.rs

@@ -202,6 +202,7 @@ pub struct TaskInfo {
     pub rank: Option<f32>,
     pub created_at: Timestamp,
     pub state: String,
+    pub bounty: Option<f32>,
     pub events: Vec<TaskEvent>,
     pub comments: Vec<Comment>,
 }
@@ -233,6 +234,12 @@ impl From<&TaskInfo> for JsonValue {
             JsonValue::Null
         };
 
+        let bounty = if let Some(bounty) = task.bounty {
+            JsonValue::Number(bounty.into())
+        } else {
+            JsonValue::Null
+        };
+
         let created_at = JsonValue::String(task.created_at.inner().to_string());
         let state = JsonValue::String(task.state.clone());
         let events: Vec<JsonValue> = task.events.iter().map(|x| x.clone().into()).collect();
@@ -251,6 +258,7 @@ impl From<&TaskInfo> for JsonValue {
             ("rank".to_string(), rank),
             ("created_at".to_string(), created_at),
             ("state".to_string(), state),
+            ("bounty".to_string(), bounty),
             ("events".to_string(), JsonValue::Array(events)),
             ("comments".to_string(), JsonValue::Array(comments)),
         ]))
@@ -282,6 +290,14 @@ impl From<JsonValue> for TaskInfo {
             }
         };
 
+        let bounty = {
+            if value["bounty"].is_null() {
+                None
+            } else {
+                Some(*value["bounty"].get::<f64>().unwrap() as f32)
+            }
+        };
+
         let created_at = {
             let u64_str = value["created_at"].get::<String>().unwrap();
             Timestamp::from_u64(u64_str.parse::<u64>().unwrap())
@@ -303,6 +319,7 @@ impl From<JsonValue> for TaskInfo {
             rank,
             created_at,
             state: value["state"].get::<String>().unwrap().clone(),
+            bounty,
             events,
             comments,
         }
@@ -318,6 +335,7 @@ impl TaskInfo {
         due: Option<Timestamp>,
         rank: Option<f32>,
         created_at: Timestamp,
+        bounty: Option<f32>,
     ) -> TaudResult<Self> {
         // generate ref_id
         let ref_id = gen_id(30);
@@ -341,6 +359,7 @@ impl TaskInfo {
             rank,
             created_at,
             state: "open".into(),
+            bounty,
             comments: vec![],
             events: vec![],
         })
@@ -447,6 +466,11 @@ impl TaskInfo {
         self.rank = r;
     }
 
+    pub fn set_bounty(&mut self, b: Option<f32>) {
+        debug!(target: "tau", "TaskInfo::set_bounty()");
+        self.bounty = b;
+    }
+
     pub fn set_due(&mut self, d: Option<Timestamp>) {
         debug!(target: "tau", "TaskInfo::set_due()");
         self.due = d;

+ 5 - 0
contrib/localnet/taud-four-nodes/taud_full_node1.toml

@@ -43,6 +43,11 @@ read_key = "AXApLyi8id3T1MwKkrgdYZtkpUag5qMmambDHGkdFiY2"
 write_key = "7jvrj4Rxnm1UcAjz5Y1CNFEfZiGMg9F1ekfbbEakkicA"
 write_public_key = "2LW4qXxR5QSybtMeRtX69GdqNWxgAbDVyMT6aWe37MT7"
 
+[workspace."test"]
+read_key = "AXApLyi8id3T1MwKkrgdYZtkpUag5qMmambDHGkdFiY2"
+write_key = "7jvrj4Rxnm1UcAjz5Y1CNFEfZiGMg9F1ekfbbEakkicA"
+write_public_key = "2LW4qXxR5QSybtMeRtX69GdqNWxgAbDVyMT6aWe37MT7"
+
 ## JSON-RPC settings
 [rpc]
 ## JSON-RPC listen URL

+ 5 - 0
contrib/localnet/taud-four-nodes/taud_full_node2.toml

@@ -43,6 +43,11 @@ read_key = "AXApLyi8id3T1MwKkrgdYZtkpUag5qMmambDHGkdFiY2"
 write_key = "7jvrj4Rxnm1UcAjz5Y1CNFEfZiGMg9F1ekfbbEakkicA"
 write_public_key = "2LW4qXxR5QSybtMeRtX69GdqNWxgAbDVyMT6aWe37MT7"
 
+[workspace."darkfi-dev"]
+read_key = "AXApLyi8id3T1MwKkrgdYZtkpUag5qMmambDHGkdFiY2"
+write_key = "7jvrj4Rxnm1UcAjz5Y1CNFEfZiGMg9F1ekfbbEakkicA"
+write_public_key = "2LW4qXxR5QSybtMeRtX69GdqNWxgAbDVyMT6aWe37MT7"
+
 ## JSON-RPC settings
 [rpc]
 ## JSON-RPC listen URL

+ 1 - 1
contrib/localnet/taud-four-nodes/tmux_sessions.sh

@@ -11,7 +11,7 @@ TAU="python $TAU_CLI/tau"
 # Source tau-cli python venv
 . $TAU_CLI/venv/bin/activate
 
-session=taud
+session=taud-local
 
 tmux new-session -d -s $session -n "seed"
 tmux send-keys -t $session "$TAUD --config seed.toml --skip-dag-sync" Enter