1use super::common::CardId;
2use super::parameter::{Parameter, ParameterMapping};
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
9#[serde(rename_all = "lowercase")]
10pub enum CardType {
11 #[default]
12 Question,
13 Metric,
14 Model,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
19#[serde(rename_all = "lowercase")]
20pub enum QueryType {
21 #[default]
22 Query,
23 Native,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct Card {
29 pub id: Option<CardId>,
30 pub name: String,
31 #[serde(rename = "type", default)]
33 pub card_type: CardType,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub description: Option<String>,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub collection_id: Option<i32>,
38 #[serde(default = "default_display")]
39 pub display: String,
40 #[serde(default)]
41 pub visualization_settings: Value,
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub dataset_query: Option<Value>,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub created_at: Option<DateTime<Utc>>,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub updated_at: Option<DateTime<Utc>>,
48 #[serde(default)]
49 pub archived: bool,
50 #[serde(default)]
51 pub enable_embedding: bool,
52 #[serde(default)]
53 pub embedding_params: Value,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub result_metadata: Option<Value>,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub creator_id: Option<i32>,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub database_id: Option<i32>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub table_id: Option<i32>,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub query_type: Option<QueryType>,
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub entity_id: Option<String>,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub cache_ttl: Option<i32>,
70 #[serde(skip_serializing_if = "Option::is_none")]
71 pub collection_position: Option<i32>,
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub dashboard_tab_id: Option<i32>,
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub dashboard_id: Option<i32>,
76 #[serde(skip_serializing_if = "Option::is_none")]
77 pub public_uuid: Option<String>,
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub made_public_by_id: Option<i32>,
80 #[serde(default)]
81 pub parameters: Vec<Parameter>,
82 #[serde(default)]
83 pub parameter_mappings: Vec<ParameterMapping>,
84}
85
86fn default_display() -> String {
87 "table".to_string()
88}
89
90impl Card {
91 pub fn new(id: Option<CardId>, name: String, card_type: CardType) -> Self {
93 Self {
94 id,
95 name,
96 card_type,
97 description: None,
98 collection_id: None,
99 display: default_display(),
100 visualization_settings: Value::Object(serde_json::Map::new()),
101 dataset_query: None,
102 created_at: None,
103 updated_at: None,
104 archived: false,
105 enable_embedding: false,
106 embedding_params: Value::Object(serde_json::Map::new()),
107 result_metadata: None,
108 creator_id: None,
109 database_id: None,
110 table_id: None,
111 query_type: None,
112 entity_id: None,
113 cache_ttl: None,
114 collection_position: None,
115 dashboard_tab_id: None,
116 dashboard_id: None,
117 public_uuid: None,
118 made_public_by_id: None,
119 parameters: Vec::new(),
120 parameter_mappings: Vec::new(),
121 }
122 }
123
124 pub fn id(&self) -> Option<CardId> {
126 self.id
127 }
128
129 pub fn name(&self) -> &str {
130 &self.name
131 }
132
133 pub fn card_type(&self) -> &CardType {
134 &self.card_type
135 }
136
137 pub fn description(&self) -> Option<&str> {
138 self.description.as_deref()
139 }
140
141 pub fn collection_id(&self) -> Option<i32> {
142 self.collection_id
143 }
144
145 pub fn display(&self) -> &str {
146 &self.display
147 }
148
149 pub fn visualization_settings(&self) -> &Value {
150 &self.visualization_settings
151 }
152
153 pub fn dataset_query(&self) -> Option<&Value> {
154 self.dataset_query.as_ref()
155 }
156
157 pub fn archived(&self) -> bool {
158 self.archived
159 }
160
161 pub fn enable_embedding(&self) -> bool {
162 self.enable_embedding
163 }
164}
165
166pub struct CardBuilder {
168 id: Option<CardId>,
169 name: String,
170 card_type: CardType,
171 description: Option<String>,
172 collection_id: Option<i32>,
173 display: String,
174 visualization_settings: Value,
175 dataset_query: Option<Value>,
176 created_at: Option<DateTime<Utc>>,
177 updated_at: Option<DateTime<Utc>>,
178 archived: bool,
179 enable_embedding: bool,
180 embedding_params: Value,
181 result_metadata: Option<Value>,
182 creator_id: Option<i32>,
183 database_id: Option<i32>,
184 table_id: Option<i32>,
185 query_type: Option<QueryType>,
186 entity_id: Option<String>,
187 cache_ttl: Option<i32>,
188 collection_position: Option<i32>,
189 dashboard_tab_id: Option<i32>,
190 dashboard_id: Option<i32>,
191 public_uuid: Option<String>,
192 made_public_by_id: Option<i32>,
193 parameters: Vec<Parameter>,
194 parameter_mappings: Vec<ParameterMapping>,
195}
196
197impl CardBuilder {
198 pub fn new(id: Option<CardId>, name: String, card_type: CardType) -> Self {
200 Self {
201 id,
202 name,
203 card_type,
204 description: None,
205 collection_id: None,
206 display: default_display(),
207 visualization_settings: Value::Object(serde_json::Map::new()),
208 dataset_query: None,
209 created_at: None,
210 updated_at: None,
211 archived: false,
212 enable_embedding: false,
213 embedding_params: Value::Object(serde_json::Map::new()),
214 result_metadata: None,
215 creator_id: None,
216 database_id: None,
217 table_id: None,
218 query_type: None,
219 entity_id: None,
220 cache_ttl: None,
221 collection_position: None,
222 dashboard_tab_id: None,
223 dashboard_id: None,
224 public_uuid: None,
225 made_public_by_id: None,
226 parameters: Vec::new(),
227 parameter_mappings: Vec::new(),
228 }
229 }
230
231 pub fn new_card(name: impl Into<String>) -> Self {
233 Self::new(None, name.into(), CardType::default())
234 }
235
236 pub fn description<S: Into<String>>(mut self, desc: S) -> Self {
237 self.description = Some(desc.into());
238 self
239 }
240
241 pub fn collection_id(mut self, id: i32) -> Self {
242 self.collection_id = Some(id);
243 self
244 }
245
246 pub fn display<S: Into<String>>(mut self, display: S) -> Self {
247 self.display = display.into();
248 self
249 }
250
251 pub fn visualization_settings(mut self, settings: Value) -> Self {
252 self.visualization_settings = settings;
253 self
254 }
255
256 pub fn dataset_query(mut self, query: Value) -> Self {
257 self.dataset_query = Some(query);
258 self
259 }
260
261 pub fn created_at(mut self, dt: DateTime<Utc>) -> Self {
262 self.created_at = Some(dt);
263 self
264 }
265
266 pub fn updated_at(mut self, dt: DateTime<Utc>) -> Self {
267 self.updated_at = Some(dt);
268 self
269 }
270
271 pub fn archived(mut self, archived: bool) -> Self {
272 self.archived = archived;
273 self
274 }
275
276 pub fn enable_embedding(mut self, enable: bool) -> Self {
277 self.enable_embedding = enable;
278 self
279 }
280
281 pub fn embedding_params(mut self, params: Value) -> Self {
282 self.embedding_params = params;
283 self
284 }
285
286 pub fn result_metadata(mut self, metadata: Value) -> Self {
287 self.result_metadata = Some(metadata);
288 self
289 }
290
291 pub fn card_type(mut self, card_type: CardType) -> Self {
292 self.card_type = card_type;
293 self
294 }
295
296 pub fn entity_id<S: Into<String>>(mut self, id: S) -> Self {
297 self.entity_id = Some(id.into());
298 self
299 }
300
301 pub fn cache_ttl(mut self, ttl: i32) -> Self {
302 self.cache_ttl = Some(ttl);
303 self
304 }
305
306 pub fn collection_position(mut self, position: i32) -> Self {
307 self.collection_position = Some(position);
308 self
309 }
310
311 pub fn dashboard_tab_id(mut self, id: i32) -> Self {
312 self.dashboard_tab_id = Some(id);
313 self
314 }
315
316 pub fn dashboard_id(mut self, id: i32) -> Self {
317 self.dashboard_id = Some(id);
318 self
319 }
320
321 pub fn parameters(mut self, params: Vec<Parameter>) -> Self {
322 self.parameters = params;
323 self
324 }
325
326 pub fn parameter_mappings(mut self, mappings: Vec<ParameterMapping>) -> Self {
327 self.parameter_mappings = mappings;
328 self
329 }
330
331 pub fn creator_id(mut self, id: i32) -> Self {
332 self.creator_id = Some(id);
333 self
334 }
335
336 pub fn database_id(mut self, id: i32) -> Self {
337 self.database_id = Some(id);
338 self
339 }
340
341 pub fn table_id(mut self, id: i32) -> Self {
342 self.table_id = Some(id);
343 self
344 }
345
346 pub fn query_type(mut self, query_type: QueryType) -> Self {
347 self.query_type = Some(query_type);
348 self
349 }
350
351 pub fn public_uuid<S: Into<String>>(mut self, uuid: S) -> Self {
352 self.public_uuid = Some(uuid.into());
353 self
354 }
355
356 pub fn made_public_by_id(mut self, id: i32) -> Self {
357 self.made_public_by_id = Some(id);
358 self
359 }
360
361 pub fn build(self) -> Card {
363 Card {
364 id: self.id,
365 name: self.name,
366 card_type: self.card_type,
367 description: self.description,
368 collection_id: self.collection_id,
369 display: self.display,
370 visualization_settings: self.visualization_settings,
371 dataset_query: self.dataset_query,
372 created_at: self.created_at,
373 updated_at: self.updated_at,
374 archived: self.archived,
375 enable_embedding: self.enable_embedding,
376 embedding_params: self.embedding_params,
377 result_metadata: self.result_metadata,
378 creator_id: self.creator_id,
379 database_id: self.database_id,
380 table_id: self.table_id,
381 query_type: self.query_type,
382 entity_id: self.entity_id,
383 cache_ttl: self.cache_ttl,
384 collection_position: self.collection_position,
385 dashboard_tab_id: self.dashboard_tab_id,
386 dashboard_id: self.dashboard_id,
387 public_uuid: self.public_uuid,
388 made_public_by_id: self.made_public_by_id,
389 parameters: self.parameters,
390 parameter_mappings: self.parameter_mappings,
391 }
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use crate::core::models::parameter::{ParameterTarget, VariableTarget};
399
400 #[test]
401 fn test_card_creation() {
402 let card = Card::new(Some(CardId(1)), "Test Card".to_string(), CardType::Question);
403
404 assert_eq!(card.id(), Some(CardId(1)));
405 assert_eq!(card.name(), "Test Card");
406 assert_eq!(card.card_type(), &CardType::Question);
407 assert!(card.description().is_none());
408 assert!(card.collection_id().is_none());
409 }
410
411 #[test]
412 fn test_card_with_builder() {
413 let card = CardBuilder::new(
414 Some(CardId(2)),
415 "Builder Card".to_string(),
416 CardType::Metric,
417 )
418 .description("A test card created with builder")
419 .collection_id(10)
420 .display("table")
421 .cache_ttl(300)
422 .build();
423
424 assert_eq!(card.id(), Some(CardId(2)));
425 assert_eq!(card.name(), "Builder Card");
426 assert_eq!(card.card_type(), &CardType::Metric);
427 assert_eq!(card.description(), Some("A test card created with builder"));
428 assert_eq!(card.collection_id(), Some(10));
429 assert_eq!(card.display(), "table");
430 assert_eq!(card.cache_ttl, Some(300));
431 }
432
433 #[test]
434 fn test_card_deserialize_from_json() {
435 let json_str = r#"{
436 "id": 123,
437 "name": "Sales Dashboard Card",
438 "type": "question",
439 "description": "Monthly sales overview",
440 "collection_id": 5,
441 "display": "line",
442 "visualization_settings": {
443 "graph.dimensions": ["date"],
444 "graph.metrics": ["count"]
445 },
446 "created_at": "2023-08-08T10:00:00Z",
447 "updated_at": "2023-08-08T12:00:00Z",
448 "archived": false,
449 "enable_embedding": true,
450 "cache_ttl": 600,
451 "entity_id": "abc123",
452 "creator_id": 10,
453 "database_id": 1,
454 "table_id": 42,
455 "query_type": "native",
456 "public_uuid": "1234-5678-9012",
457 "made_public_by_id": 15
458 }"#;
459
460 let card: Card = serde_json::from_str(json_str).unwrap();
461
462 assert_eq!(card.id(), Some(CardId(123)));
463 assert_eq!(card.name(), "Sales Dashboard Card");
464 assert_eq!(card.card_type(), &CardType::Question);
465 assert_eq!(card.description(), Some("Monthly sales overview"));
466 assert_eq!(card.collection_id(), Some(5));
467 assert_eq!(card.display(), "line");
468 assert!(!card.archived());
469 assert!(card.enable_embedding());
470 assert_eq!(card.cache_ttl, Some(600));
471 assert_eq!(card.entity_id, Some("abc123".to_string()));
472 assert_eq!(card.creator_id, Some(10));
473 assert_eq!(card.database_id, Some(1));
474 assert_eq!(card.table_id, Some(42));
475 assert_eq!(card.query_type, Some(QueryType::Native));
476 assert_eq!(card.public_uuid, Some("1234-5678-9012".to_string()));
477 assert_eq!(card.made_public_by_id, Some(15));
478 }
479
480 #[test]
481 fn test_card_type_serialization() {
482 assert_eq!(
483 serde_json::to_string(&CardType::Question).unwrap(),
484 r#""question""#
485 );
486 assert_eq!(
487 serde_json::to_string(&CardType::Metric).unwrap(),
488 r#""metric""#
489 );
490 assert_eq!(
491 serde_json::to_string(&CardType::Model).unwrap(),
492 r#""model""#
493 );
494 }
495
496 #[test]
497 fn test_query_type_serialization() {
498 assert_eq!(
499 serde_json::to_string(&QueryType::Query).unwrap(),
500 r#""query""#
501 );
502 assert_eq!(
503 serde_json::to_string(&QueryType::Native).unwrap(),
504 r#""native""#
505 );
506 }
507
508 #[test]
509 fn test_card_with_new_fields() {
510 let card = CardBuilder::new(
511 Some(CardId(100)),
512 "Analytics Card".to_string(),
513 CardType::Question,
514 )
515 .description("Advanced analytics")
516 .database_id(1)
517 .table_id(5)
518 .query_type(QueryType::Native)
519 .creator_id(42)
520 .public_uuid("uuid-1234")
521 .made_public_by_id(10)
522 .build();
523
524 assert_eq!(card.database_id, Some(1));
525 assert_eq!(card.table_id, Some(5));
526 assert_eq!(card.query_type, Some(QueryType::Native));
527 assert_eq!(card.creator_id, Some(42));
528 assert_eq!(card.public_uuid, Some("uuid-1234".to_string()));
529 assert_eq!(card.made_public_by_id, Some(10));
530 }
531
532 #[test]
533 fn test_card_with_parameters() {
534 let parameter = Parameter {
535 id: "date_param".to_string(),
536 param_type: "date/relative".to_string(),
537 name: "Date Filter".to_string(),
538 slug: "date".to_string(),
539 default: Some(serde_json::json!("past7days")),
540 required: false,
541 options: None,
542 values_source_type: None,
543 values_source_config: None,
544 };
545
546 let parameter_mapping = ParameterMapping {
547 parameter_id: "date_param".to_string(),
548 card_id: 100,
549 target: ParameterTarget::Variable(VariableTarget {
550 target_type: "variable".to_string(),
551 id: "start_date".to_string(),
552 }),
553 };
554
555 let card = CardBuilder::new(
556 Some(CardId(100)),
557 "Parameterized Card".to_string(),
558 CardType::Question,
559 )
560 .parameters(vec![parameter.clone()])
561 .parameter_mappings(vec![parameter_mapping.clone()])
562 .build();
563
564 assert_eq!(card.parameters.len(), 1);
565 assert_eq!(card.parameters[0].id, "date_param");
566 assert_eq!(card.parameter_mappings.len(), 1);
567 assert_eq!(card.parameter_mappings[0].parameter_id, "date_param");
568 }
569}