1use crate::client::ClickUpClient;
2use crate::commands::auth::resolve_token;
3use crate::commands::workspace::resolve_workspace;
4use crate::error::CliError;
5use crate::output::OutputConfig;
6use crate::Cli;
7use clap::Subcommand;
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!({
151 "name": name,
152 "due_date": due_date,
153 "description": description,
154 "multiple_owners": false,
155 });
156 if let Some(c) = color {
157 body["color"] = serde_json::Value::String(c);
158 }
159 if let Some(o) = owner {
160 body["owners"] = serde_json::json!([o]);
161 }
162 let resp = client
163 .post(&format!("/v2/team/{}/goal", ws_id), &body)
164 .await?;
165 let goal = resp.get("goal").cloned().unwrap_or(resp);
166 output.print_single(&goal, GOAL_FIELDS, "id");
167 Ok(())
168 }
169 GoalCommands::Update {
170 id,
171 name,
172 due_date,
173 description,
174 color,
175 add_owner,
176 rem_owner,
177 } => {
178 let mut body = serde_json::Map::new();
179 if let Some(n) = name {
180 body.insert("name".into(), serde_json::Value::String(n));
181 }
182 if let Some(d) = due_date {
183 body.insert("due_date".into(), serde_json::Value::String(d));
184 }
185 if let Some(d) = description {
186 body.insert("description".into(), serde_json::Value::String(d));
187 }
188 if let Some(c) = color {
189 body.insert("color".into(), serde_json::Value::String(c));
190 }
191 if let Some(o) = add_owner {
192 body.insert("add_owners".into(), serde_json::json!([o]));
193 }
194 if let Some(o) = rem_owner {
195 body.insert("rem_owners".into(), serde_json::json!([o]));
196 }
197 let resp = client
198 .put(
199 &format!("/v2/goal/{}", id),
200 &serde_json::Value::Object(body),
201 )
202 .await?;
203 let goal = resp.get("goal").cloned().unwrap_or(resp);
204 output.print_single(&goal, GOAL_FIELDS, "id");
205 Ok(())
206 }
207 GoalCommands::Delete { id } => {
208 client.delete(&format!("/v2/goal/{}", id)).await?;
209 output.print_message(&format!("Goal {} deleted", id));
210 Ok(())
211 }
212 GoalCommands::AddKr {
213 goal_id,
214 name,
215 kr_type,
216 steps_start,
217 steps_end,
218 unit,
219 owner,
220 } => {
221 let mut body = serde_json::json!({
222 "name": name,
223 "type": kr_type,
224 "steps_start": steps_start,
225 "steps_end": steps_end,
226 });
227 if let Some(u) = unit {
228 body["unit"] = serde_json::Value::String(u);
229 }
230 if let Some(o) = owner {
231 body["owners"] = serde_json::json!([o]);
232 }
233 let resp = client
234 .post(&format!("/v2/goal/{}/key_result", goal_id), &body)
235 .await?;
236 let kr = resp.get("key_result").cloned().unwrap_or(resp);
237 output.print_single(
238 &kr,
239 &["id", "name", "type", "steps_start", "steps_end"],
240 "id",
241 );
242 Ok(())
243 }
244 GoalCommands::UpdateKr {
245 kr_id,
246 steps_current,
247 note,
248 } => {
249 let mut body = serde_json::json!({ "steps_current": steps_current });
250 if let Some(n) = note {
251 body["note"] = serde_json::Value::String(n);
252 }
253 let resp = client
254 .put(&format!("/v2/key_result/{}", kr_id), &body)
255 .await?;
256 let kr = resp.get("key_result").cloned().unwrap_or(resp);
257 output.print_single(&kr, &["id", "name", "steps_current", "steps_end"], "id");
258 Ok(())
259 }
260 GoalCommands::DeleteKr { kr_id } => {
261 client.delete(&format!("/v2/key_result/{}", kr_id)).await?;
262 output.print_message(&format!("Key result {} deleted", kr_id));
263 Ok(())
264 }
265 }
266}