Skip to main content

clickup_cli/commands/
view.rs

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 ViewCommands {
11    /// List views (use one scope flag: --workspace-level, --space, --folder, --list)
12    List {
13        /// List workspace-level views
14        #[arg(long = "workspace-level", conflicts_with_all = &["space", "folder", "list"])]
15        workspace_level: bool,
16        /// Space ID
17        #[arg(long, conflicts_with_all = &["workspace", "folder", "list"])]
18        space: Option<String>,
19        /// Folder ID
20        #[arg(long, conflicts_with_all = &["workspace", "space", "list"])]
21        folder: Option<String>,
22        /// List ID
23        #[arg(long, conflicts_with_all = &["workspace", "space", "folder"])]
24        list: Option<String>,
25    },
26    /// Get a view by ID
27    Get {
28        /// View ID
29        id: String,
30    },
31    /// Create a view (use one scope flag: --workspace-level, --space, --folder, --list)
32    Create {
33        /// View name
34        #[arg(long)]
35        name: String,
36        /// View type (list, board, calendar, gantt, activity, map, workload, table, doc, chat, embed)
37        #[arg(long, name = "type")]
38        view_type: String,
39        /// Create workspace-level view
40        #[arg(long = "workspace-level", conflicts_with_all = &["space", "folder", "list"])]
41        workspace_level: bool,
42        /// Space ID
43        #[arg(long, conflicts_with_all = &["workspace", "folder", "list"])]
44        space: Option<String>,
45        /// Folder ID
46        #[arg(long, conflicts_with_all = &["workspace", "space", "list"])]
47        folder: Option<String>,
48        /// List ID
49        #[arg(long, conflicts_with_all = &["workspace", "space", "folder"])]
50        list: Option<String>,
51    },
52    /// Update a view
53    Update {
54        /// View ID
55        id: String,
56        /// New name
57        #[arg(long)]
58        name: Option<String>,
59    },
60    /// Delete a view
61    Delete {
62        /// View ID
63        id: String,
64    },
65    /// List tasks in a view
66    Tasks {
67        /// View ID
68        id: String,
69        /// Page number (0-indexed)
70        #[arg(long, default_value = "0")]
71        page: u32,
72    },
73}
74
75const VIEW_FIELDS: &[&str] = &["id", "name", "type"];
76
77pub async fn execute(command: ViewCommands, cli: &Cli) -> Result<(), CliError> {
78    let token = resolve_token(cli)?;
79    let client = ClickUpClient::new(&token, cli.timeout)?;
80    let output = OutputConfig::from_cli(&cli.output, &cli.fields, cli.no_header, cli.quiet);
81
82    match command {
83        ViewCommands::List {
84            workspace_level: workspace,
85            space,
86            folder,
87            list,
88        } => {
89            let url = if workspace {
90                let ws_id = resolve_workspace(cli)?;
91                format!("/v2/team/{}/view", ws_id)
92            } else if let Some(id) = space {
93                format!("/v2/space/{}/view", id)
94            } else if let Some(id) = folder {
95                format!("/v2/folder/{}/view", id)
96            } else if let Some(id) = list {
97                format!("/v2/list/{}/view", id)
98            } else {
99                return Err(CliError::ClientError {
100                    message: "Specify a scope: --workspace, --space ID, --folder ID, or --list ID"
101                        .into(),
102                    status: 0,
103                });
104            };
105            let resp = client.get(&url).await?;
106            let views = resp
107                .get("views")
108                .and_then(|v| v.as_array())
109                .cloned()
110                .unwrap_or_default();
111            output.print_items(&views, VIEW_FIELDS, "id");
112            Ok(())
113        }
114        ViewCommands::Get { id } => {
115            let resp = client.get(&format!("/v2/view/{}", id)).await?;
116            let view = resp.get("view").cloned().unwrap_or(resp);
117            output.print_single(&view, VIEW_FIELDS, "id");
118            Ok(())
119        }
120        ViewCommands::Create {
121            name,
122            view_type,
123            workspace_level: workspace,
124            space,
125            folder,
126            list,
127        } => {
128            let url = if workspace {
129                let ws_id = resolve_workspace(cli)?;
130                format!("/v2/team/{}/view", ws_id)
131            } else if let Some(id) = space {
132                format!("/v2/space/{}/view", id)
133            } else if let Some(id) = folder {
134                format!("/v2/folder/{}/view", id)
135            } else if let Some(id) = list {
136                format!("/v2/list/{}/view", id)
137            } else {
138                return Err(CliError::ClientError {
139                    message: "Specify a scope: --workspace, --space ID, --folder ID, or --list ID"
140                        .into(),
141                    status: 0,
142                });
143            };
144            let body = serde_json::json!({
145                "name": name,
146                "type": view_type,
147            });
148            let resp = client.post(&url, &body).await?;
149            let view = resp.get("view").cloned().unwrap_or(resp);
150            output.print_single(&view, VIEW_FIELDS, "id");
151            Ok(())
152        }
153        ViewCommands::Update { id, name } => {
154            let mut body = serde_json::Map::new();
155            if let Some(n) = name {
156                body.insert("name".into(), serde_json::Value::String(n));
157            }
158            let resp = client
159                .put(
160                    &format!("/v2/view/{}", id),
161                    &serde_json::Value::Object(body),
162                )
163                .await?;
164            let view = resp.get("view").cloned().unwrap_or(resp);
165            output.print_single(&view, VIEW_FIELDS, "id");
166            Ok(())
167        }
168        ViewCommands::Delete { id } => {
169            client.delete(&format!("/v2/view/{}", id)).await?;
170            output.print_message(&format!("View {} deleted", id));
171            Ok(())
172        }
173        ViewCommands::Tasks { id, page } => {
174            let resp = client
175                .get(&format!("/v2/view/{}/task?page={}", id, page))
176                .await?;
177            let tasks = resp
178                .get("tasks")
179                .and_then(|t| t.as_array())
180                .cloned()
181                .unwrap_or_default();
182            output.print_items(&tasks, &["id", "name", "status", "assignees"], "id");
183            Ok(())
184        }
185    }
186}