1use clap::Subcommand;
2use crate::client::ClickUpClient;
3use crate::commands::auth::resolve_token;
4use crate::commands::workspace::resolve_workspace;
5use crate::error::CliError;
6use crate::output::OutputConfig;
7use crate::Cli;
8
9#[derive(Subcommand)]
10pub enum GoalCommands {
11 List {
13 #[arg(long)]
15 include_completed: bool,
16 },
17 Get {
19 id: String,
21 },
22 Create {
24 #[arg(long)]
26 name: String,
27 #[arg(long)]
29 due_date: String,
30 #[arg(long)]
32 description: String,
33 #[arg(long)]
35 color: Option<String>,
36 #[arg(long)]
38 owner: Option<String>,
39 },
40 Update {
42 id: String,
44 #[arg(long)]
46 name: Option<String>,
47 #[arg(long)]
49 due_date: Option<String>,
50 #[arg(long)]
52 description: Option<String>,
53 #[arg(long)]
55 color: Option<String>,
56 #[arg(long)]
58 add_owner: Option<String>,
59 #[arg(long)]
61 rem_owner: Option<String>,
62 },
63 Delete {
65 id: String,
67 },
68 AddKr {
70 goal_id: String,
72 #[arg(long)]
74 name: String,
75 #[arg(long, name = "type")]
77 kr_type: String,
78 #[arg(long)]
80 steps_start: i64,
81 #[arg(long)]
83 steps_end: i64,
84 #[arg(long)]
86 unit: Option<String>,
87 #[arg(long)]
89 owner: Option<String>,
90 },
91 UpdateKr {
93 kr_id: String,
95 #[arg(long)]
97 steps_current: i64,
98 #[arg(long)]
100 note: Option<String>,
101 },
102 DeleteKr {
104 kr_id: String,
106 },
107}
108
109const GOAL_FIELDS: &[&str] = &["id", "name", "percent_completed", "due_date"];
110
111pub async fn execute(command: GoalCommands, cli: &Cli) -> Result<(), CliError> {
112 let token = resolve_token(cli)?;
113 let client = ClickUpClient::new(&token, cli.timeout)?;
114 let output = OutputConfig::from_cli(&cli.output, &cli.fields, cli.no_header, cli.quiet);
115
116 match command {
117 GoalCommands::List { include_completed } => {
118 let ws_id = resolve_workspace(cli)?;
119 let resp = client
120 .get(&format!(
121 "/v2/team/{}/goal?include_completed={}",
122 ws_id, include_completed
123 ))
124 .await?;
125 let goals = resp
126 .get("goals")
127 .and_then(|g| g.as_array())
128 .cloned()
129 .unwrap_or_default();
130 output.print_items(&goals, GOAL_FIELDS, "id");
131 Ok(())
132 }
133 GoalCommands::Get { id } => {
134 let resp = client.get(&format!("/v2/goal/{}", id)).await?;
135 let goal = resp.get("goal").cloned().unwrap_or(resp);
136 output.print_single(&goal, GOAL_FIELDS, "id");
137 Ok(())
138 }
139 GoalCommands::Create {
140 name,
141 due_date,
142 description,
143 color,
144 owner,
145 } => {
146 let ws_id = resolve_workspace(cli)?;
147 let mut body = serde_json::json!({
148 "name": name,
149 "due_date": due_date,
150 "description": description,
151 });
152 if let Some(c) = color {
153 body["color"] = serde_json::Value::String(c);
154 }
155 if let Some(o) = owner {
156 body["owners"] = serde_json::json!([o]);
157 }
158 let resp = client
159 .post(&format!("/v2/team/{}/goal", ws_id), &body)
160 .await?;
161 let goal = resp.get("goal").cloned().unwrap_or(resp);
162 output.print_single(&goal, GOAL_FIELDS, "id");
163 Ok(())
164 }
165 GoalCommands::Update {
166 id,
167 name,
168 due_date,
169 description,
170 color,
171 add_owner,
172 rem_owner,
173 } => {
174 let mut body = serde_json::Map::new();
175 if let Some(n) = name {
176 body.insert("name".into(), serde_json::Value::String(n));
177 }
178 if let Some(d) = due_date {
179 body.insert("due_date".into(), serde_json::Value::String(d));
180 }
181 if let Some(d) = description {
182 body.insert("description".into(), serde_json::Value::String(d));
183 }
184 if let Some(c) = color {
185 body.insert("color".into(), serde_json::Value::String(c));
186 }
187 if let Some(o) = add_owner {
188 body.insert("add_owners".into(), serde_json::json!([o]));
189 }
190 if let Some(o) = rem_owner {
191 body.insert("rem_owners".into(), serde_json::json!([o]));
192 }
193 let resp = client
194 .put(&format!("/v2/goal/{}", id), &serde_json::Value::Object(body))
195 .await?;
196 let goal = resp.get("goal").cloned().unwrap_or(resp);
197 output.print_single(&goal, GOAL_FIELDS, "id");
198 Ok(())
199 }
200 GoalCommands::Delete { id } => {
201 client.delete(&format!("/v2/goal/{}", id)).await?;
202 output.print_message(&format!("Goal {} deleted", id));
203 Ok(())
204 }
205 GoalCommands::AddKr {
206 goal_id,
207 name,
208 kr_type,
209 steps_start,
210 steps_end,
211 unit,
212 owner,
213 } => {
214 let mut body = serde_json::json!({
215 "name": name,
216 "type": kr_type,
217 "steps_start": steps_start,
218 "steps_end": steps_end,
219 });
220 if let Some(u) = unit {
221 body["unit"] = serde_json::Value::String(u);
222 }
223 if let Some(o) = owner {
224 body["owners"] = serde_json::json!([o]);
225 }
226 let resp = client
227 .post(&format!("/v2/goal/{}/key_result", goal_id), &body)
228 .await?;
229 let kr = resp.get("key_result").cloned().unwrap_or(resp);
230 output.print_single(&kr, &["id", "name", "type", "steps_start", "steps_end"], "id");
231 Ok(())
232 }
233 GoalCommands::UpdateKr {
234 kr_id,
235 steps_current,
236 note,
237 } => {
238 let mut body = serde_json::json!({ "steps_current": steps_current });
239 if let Some(n) = note {
240 body["note"] = serde_json::Value::String(n);
241 }
242 let resp = client
243 .put(&format!("/v2/key_result/{}", kr_id), &body)
244 .await?;
245 let kr = resp.get("key_result").cloned().unwrap_or(resp);
246 output.print_single(&kr, &["id", "name", "steps_current", "steps_end"], "id");
247 Ok(())
248 }
249 GoalCommands::DeleteKr { kr_id } => {
250 client.delete(&format!("/v2/key_result/{}", kr_id)).await?;
251 output.print_message(&format!("Key result {} deleted", kr_id));
252 Ok(())
253 }
254 }
255}