1use std::collections::HashMap;
4use std::convert::TryFrom;
5
6use prost_types::value::Kind;
7use tonic::Request;
8use tonic::transport::Channel;
9
10use crate::anytype::ClientCommandsClient;
11use crate::anytype::rpc::object::show::Request as ObjectShowRequest;
12use crate::auth::with_token;
13use crate::error::AuthError;
14pub use crate::error::ViewError;
15use crate::model;
16use crate::model::block::ContentValue;
17use crate::model::block::content::Dataview as BlockDataview;
18use crate::model::block::content::dataview;
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct GridViewColumn {
23 pub relation_key: String,
24 pub name: String,
25 pub format: Option<model::RelationFormat>,
26 pub formula: dataview::relation::FormulaType,
27 pub is_visible: bool,
28 pub width: i32,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct GridViewInfo {
34 pub block_id: String,
35 pub view_id: String,
36 pub view_name: String,
37 pub columns: Vec<GridViewColumn>,
38}
39
40pub async fn fetch_grid_view_columns(
42 client: &mut ClientCommandsClient<Channel>,
43 token: &str,
44 space_id: &str,
45 type_id: &str,
46 view_id: &str,
47) -> Result<GridViewInfo, ViewError> {
48 let request = ObjectShowRequest {
49 object_id: type_id.to_string(),
50 space_id: space_id.to_string(),
51 include_relations_as_dependent_objects: true,
52 ..Default::default()
53 };
54 let request = with_token(Request::new(request), token).map_err(|err: AuthError| {
55 ViewError::ApiResponse {
56 code: 0,
57 description: err.to_string(),
58 }
59 })?;
60
61 let response = client.object_show(request).await?.into_inner();
62 if let Some(error) = response.error
63 && error.code != 0
64 {
65 return Err(ViewError::ApiResponse {
66 code: error.code,
67 description: error.description,
68 });
69 }
70
71 let object_view = response.object_view.ok_or(ViewError::MissingObjectView)?;
72 let relation_names = relation_name_index(&object_view.details);
73
74 let (block_id, dataview) = find_dataview_block(&object_view.blocks, view_id)?;
75 let view = dataview
76 .views
77 .iter()
78 .find(|view| view.id == view_id)
79 .ok_or_else(|| ViewError::MissingView {
80 view_id: view_id.to_string(),
81 })?;
82
83 let view_type =
84 dataview::view::Type::try_from(view.r#type).unwrap_or(dataview::view::Type::Table);
85 if view_type != dataview::view::Type::Table && view_type != dataview::view::Type::List {
86 return Err(ViewError::NotSupportedView {
87 view_id: view_id.to_string(),
88 actual: view.r#type,
89 });
90 }
91
92 let relation_formats = relation_format_index(&dataview);
93 let columns = view
94 .relations
95 .iter()
96 .map(|relation| {
97 let formula = dataview::relation::FormulaType::try_from(relation.formula)
98 .unwrap_or(dataview::relation::FormulaType::None);
99 let name = relation_names
100 .get(&relation.key)
101 .cloned()
102 .unwrap_or_else(|| relation.key.clone());
103 let format = relation_formats.get(&relation.key).cloned();
104
105 GridViewColumn {
106 relation_key: relation.key.clone(),
107 name,
108 format,
109 formula,
110 is_visible: relation.is_visible,
111 width: relation.width,
112 }
113 })
114 .collect::<Vec<_>>();
115
116 Ok(GridViewInfo {
117 block_id,
118 view_id: view.id.clone(),
119 view_name: view.name.clone(),
120 columns,
121 })
122}
123
124fn find_dataview_block(
125 blocks: &[model::Block],
126 view_id: &str,
127) -> Result<(String, BlockDataview), ViewError> {
128 for block in blocks {
129 if let Some(ContentValue::Dataview(dataview)) = block.content_value.as_ref()
130 && dataview.views.iter().any(|view| view.id == view_id)
131 {
132 return Ok((block.id.clone(), dataview.clone()));
133 }
134 }
135
136 Err(ViewError::MissingDataviewBlock {
137 view_id: view_id.to_string(),
138 })
139}
140
141fn relation_format_index(dataview: &BlockDataview) -> HashMap<String, model::RelationFormat> {
142 let mut map = HashMap::new();
143 for link in &dataview.relation_links {
144 if let Ok(format) = model::RelationFormat::try_from(link.format) {
145 map.insert(link.key.clone(), format);
146 }
147 }
148 map
149}
150
151fn relation_name_index(details: &[model::object_view::DetailsSet]) -> HashMap<String, String> {
152 let mut map = HashMap::new();
153 for detail_set in details {
154 let Some(details) = detail_set.details.as_ref() else {
155 continue;
156 };
157 let relation_key =
158 string_field(details, "relationKey").or_else(|| string_field(details, "key"));
159 let name = string_field(details, "name");
160 if let (Some(relation_key), Some(name)) = (relation_key, name) {
161 map.insert(relation_key, name);
162 }
163 }
164 map
165}
166
167fn string_field(details: &prost_types::Struct, key: &str) -> Option<String> {
168 details.fields.get(key).and_then(|value| match &value.kind {
169 Some(Kind::StringValue(value)) => Some(value.clone()),
170 _ => None,
171 })
172}