jirust_cli/runners/jira_cmd_runners/version_cmd_runner.rs
1use std::collections::HashMap;
2use std::sync::Arc;
3
4use crate::args::commands::VersionArgs;
5use crate::config::config_file::{AuthData, ConfigFile};
6use crate::jira_doc_std_field;
7use crate::utils::changelog_extractor::ChangelogExtractor;
8use chrono::Utc;
9use jira_v3_openapi::apis::Error;
10use jira_v3_openapi::apis::configuration::Configuration;
11use jira_v3_openapi::apis::issues_api::{assign_issue, do_transition, edit_issue, get_transitions};
12use jira_v3_openapi::apis::project_versions_api::*;
13use jira_v3_openapi::models::user::AccountType;
14use jira_v3_openapi::models::{
15 DeleteAndReplaceVersionBean, FieldUpdateOperation, IssueTransition, IssueUpdateDetails, User,
16 Version, VersionRelatedWork,
17};
18use serde_json::Value;
19
20#[cfg(any(windows, unix))]
21use futures::StreamExt;
22#[cfg(any(windows, unix))]
23use futures::stream::FuturesUnordered;
24
25/// Version command runner struct
26///
27/// This struct is responsible for holding the version command runner parameters
28/// and it is used to pass the parameters to the version commands runner
29#[derive(Clone)]
30pub struct VersionCmdRunner {
31 cfg: Configuration,
32 resolution_value: Value,
33 resolution_comment: Value,
34 resolution_transition_name: Option<Vec<String>>,
35}
36
37/// Version command runner implementation.
38///
39///
40/// # Methods
41///
42/// * `new` - This method creates a new instance of the VersionCmdRunner struct
43/// * `create_jira_version` - This method creates a new Jira version
44/// * `get_jira_version` - This method gets a Jira version
45/// * `list_jira_versions` - This method lists Jira versions
46/// * `update_jira_version` - This method updates a Jira version
47/// * `delete_jira_version` - This method deletes a Jira version
48/// * `release_jira_version` - This method releases a Jira version
49/// * `archive_jira_version` - This method archives a Jira version
50impl VersionCmdRunner {
51 /// This method creates a new instance of the VersionCmdRunner struct
52 ///
53 /// # Arguments
54 ///
55 /// * `cfg_file` - A ConfigFile struct
56 ///
57 /// # Returns
58 ///
59 /// * A new instance of the VersionCmdRunner struct
60 ///
61 /// # Examples
62 ///
63 /// ```
64 /// use jirust_cli::config::config_file::ConfigFile;
65 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdRunner;
66 /// use toml::Table;
67 ///
68 /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
69 ///
70 /// let version_cmd_runner = VersionCmdRunner::new(&cfg_file);
71 /// ```
72 pub fn new(cfg_file: &ConfigFile) -> VersionCmdRunner {
73 let mut config = Configuration::new();
74 let auth_data = AuthData::from_base64(cfg_file.get_auth_key());
75 config.base_path = cfg_file.get_jira_url().to_string();
76 config.basic_auth = Some((auth_data.0, Some(auth_data.1)));
77 VersionCmdRunner {
78 cfg: config,
79 resolution_value: serde_json::from_str(cfg_file.get_standard_resolution().as_str())
80 .unwrap_or(Value::Null),
81 resolution_comment: serde_json::from_str(
82 format!(
83 "{{\"body\": {}}}",
84 jira_doc_std_field!(cfg_file.get_standard_resolution_comment().as_str())
85 )
86 .as_str(),
87 )
88 .unwrap_or(Value::Null),
89 resolution_transition_name: cfg_file.get_transition_name("resolve"),
90 }
91 }
92
93 /// This method creates a new Jira version with the given parameters
94 /// and returns the created version
95 ///
96 /// # Arguments
97 ///
98 /// * `params` - A VersionCmdParams struct
99 ///
100 /// # Returns
101 ///
102 /// * A Result containing a Version struct or a Box<dyn std::error::Error>
103 ///
104 /// # Examples
105 ///
106 /// ```no_run
107 /// use jira_v3_openapi::models::Version;
108 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdParams;
109 /// use jirust_cli::config::config_file::ConfigFile;
110 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdRunner;
111 /// use toml::Table;
112 ///
113 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
114 /// # tokio_test::block_on(async {
115 /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
116 ///
117 /// let version_cmd_runner = VersionCmdRunner::new(&cfg_file);
118 /// let params = VersionCmdParams::new();
119 ///
120 /// let version = version_cmd_runner.create_jira_version(params).await?;
121 /// # Ok(())
122 /// # })
123 /// # }
124 /// ```
125 #[cfg(any(windows, unix))]
126 pub async fn create_jira_version(
127 &self,
128 params: VersionCmdParams,
129 ) -> Result<(Version, Option<Vec<(String, String, String, String)>>), Box<dyn std::error::Error>>
130 {
131 let version_description: Option<String>;
132 let mut resolved_issues = vec![];
133 let mut transitioned_issue: Arc<Vec<(String, String, String, String)>> = Arc::new(vec![]);
134 if Option::is_some(¶ms.changelog_file) {
135 let changelog_extractor = ChangelogExtractor::new(params.changelog_file.unwrap());
136 version_description = Some(changelog_extractor.extract_version_changelog().unwrap_or(
137 if Option::is_some(¶ms.version_description) {
138 params.version_description.unwrap()
139 } else {
140 "No changelog found for this version".to_string()
141 },
142 ));
143 if Option::is_some(¶ms.transition_issues) && params.transition_issues.unwrap() {
144 resolved_issues = changelog_extractor
145 .extract_issues_from_changelog(
146 &version_description.clone().unwrap(),
147 ¶ms.project,
148 )
149 .unwrap_or_default();
150 }
151 } else {
152 version_description = params.version_description;
153 }
154 let release_date =
155 if Option::is_some(¶ms.version_released) && params.version_released.unwrap() {
156 if Option::is_some(¶ms.version_release_date) {
157 params.version_release_date
158 } else {
159 Some(Utc::now().format("%Y-%m-%d").to_string())
160 }
161 } else {
162 None
163 };
164 let version = Version {
165 project: Some(params.project),
166 name: Some(
167 params
168 .version_name
169 .expect("VersionName is mandatory on creation!"),
170 ),
171 description: version_description,
172 start_date: params.version_start_date,
173 release_date,
174 archived: params.version_archived,
175 released: params.version_released,
176 ..Default::default()
177 };
178 let version = create_version(&self.cfg, version).await?;
179 if !resolved_issues.is_empty() {
180 let user_data = if Option::is_some(¶ms.transition_assignee) {
181 Some(User {
182 account_id: Some(params.transition_assignee.expect("Assignee is required")),
183 account_type: Some(AccountType::Atlassian),
184 ..Default::default()
185 })
186 } else {
187 None
188 };
189 let mut handles = FuturesUnordered::new();
190 for issue in resolved_issues {
191 handles.push(self.manage_version_related_issues(issue, &user_data, &version));
192 }
193 while let Some(result) = handles.next().await {
194 match result {
195 Ok((issue, transition_result, assign_result, fixversion_result)) => {
196 Arc::make_mut(&mut transitioned_issue).push((
197 issue,
198 transition_result,
199 assign_result,
200 fixversion_result,
201 ));
202 }
203 Err(err) => {
204 eprintln!("Error managing version related issues: {err:?}");
205 }
206 }
207 }
208 }
209 let transitioned_issue_owned: Vec<(String, String, String, String)> =
210 (*transitioned_issue).clone();
211 Ok((
212 version,
213 if !transitioned_issue.is_empty() {
214 Some(transitioned_issue_owned)
215 } else {
216 None
217 },
218 ))
219 }
220
221 /// This method creates a new Jira version with the given parameters
222 /// and returns the created version
223 ///
224 /// # Arguments
225 ///
226 /// * `params` - A VersionCmdParams struct
227 ///
228 /// # Returns
229 ///
230 /// * A Result containing a Version struct or a Box<dyn std::error::Error>
231 ///
232 /// # Examples
233 ///
234 /// ```no_run
235 /// use jira_v3_openapi::models::Version;
236 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdParams;
237 /// use jirust_cli::config::config_file::ConfigFile;
238 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdRunner;
239 /// use toml::Table;
240 ///
241 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
242 /// # tokio_test::block_on(async {
243 /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
244 ///
245 /// let version_cmd_runner = VersionCmdRunner::new(&cfg_file);
246 /// let params = VersionCmdParams::new();
247 ///
248 /// let version = version_cmd_runner.create_jira_version(params).await?;
249 /// # Ok(())
250 /// # })
251 /// # }
252 /// ```
253 #[cfg(target_family = "wasm")]
254 pub async fn create_jira_version(
255 &self,
256 params: VersionCmdParams,
257 ) -> Result<(Version, Option<Vec<(String, String, String, String)>>), Box<dyn std::error::Error>>
258 {
259 let version_description: Option<String>;
260 let mut resolved_issues = vec![];
261 let mut transitioned_issue: Vec<(String, String, String, String)> = vec![];
262 if Option::is_some(¶ms.changelog_file) {
263 let changelog_extractor = ChangelogExtractor::new(params.changelog_file.unwrap());
264 version_description = Some(changelog_extractor.extract_version_changelog().unwrap_or(
265 if Option::is_some(¶ms.version_description) {
266 params.version_description.unwrap()
267 } else {
268 "No changelog found for this version".to_string()
269 },
270 ));
271 if Option::is_some(¶ms.transition_issues) && params.transition_issues.unwrap() {
272 resolved_issues = changelog_extractor
273 .extract_issues_from_changelog(
274 &version_description.clone().unwrap(),
275 ¶ms.project,
276 )
277 .unwrap_or_default();
278 }
279 } else {
280 version_description = params.version_description;
281 }
282 let release_date =
283 if Option::is_some(¶ms.version_released) && params.version_released.unwrap() {
284 if Option::is_some(¶ms.version_release_date) {
285 params.version_release_date
286 } else {
287 Some(Utc::now().format("%Y-%m-%d").to_string())
288 }
289 } else {
290 None
291 };
292 let version = Version {
293 project: Some(params.project),
294 name: Some(
295 params
296 .version_name
297 .expect("VersionName is mandatory on cretion!"),
298 ),
299 description: version_description,
300 start_date: params.version_start_date,
301 release_date,
302 archived: params.version_archived,
303 released: params.version_released,
304 ..Default::default()
305 };
306 let version = create_version(&self.cfg, version).await?;
307 if !resolved_issues.is_empty() {
308 let user_data = if Option::is_some(¶ms.transition_assignee) {
309 Some(User {
310 account_id: Some(params.transition_assignee.expect("Assignee is required")),
311 account_type: Some(AccountType::Atlassian),
312 ..Default::default()
313 })
314 } else {
315 None
316 };
317 for issue in resolved_issues {
318 let all_transitions: Vec<IssueTransition> = get_transitions(
319 &self.cfg,
320 issue.clone().as_str(),
321 None,
322 None,
323 None,
324 Some(false),
325 None,
326 )
327 .await?
328 .transitions
329 .unwrap_or_default();
330 let transition_names: Vec<String> = self
331 .resolution_transition_name
332 .clone()
333 .expect("Transition name is required and must be set in the config file");
334 let resolve_transitions: Vec<IssueTransition> = all_transitions
335 .into_iter()
336 .filter(|t| {
337 transition_names.contains(&t.name.clone().unwrap_or("".to_string()))
338 })
339 .collect();
340 let transition_ids = resolve_transitions
341 .into_iter()
342 .map(|t| t.id.clone().unwrap_or("".to_string()))
343 .collect::<Vec<String>>();
344 let transitions = transition_ids
345 .into_iter()
346 .map(|id| {
347 Some(IssueTransition {
348 id: Some(id),
349 ..Default::default()
350 })
351 })
352 .collect::<Vec<Option<IssueTransition>>>();
353 let mut update_fields_hashmap: HashMap<String, Vec<FieldUpdateOperation>> =
354 HashMap::new();
355 let mut transition_fields_hashmap: HashMap<String, Vec<FieldUpdateOperation>> =
356 HashMap::new();
357 let mut version_update_op = FieldUpdateOperation::new();
358 let mut version_resolution_update_field = HashMap::new();
359 let mut version_resolution_comment_op = FieldUpdateOperation::new();
360 let version_json: Value =
361 serde_json::from_str(serde_json::to_string(&version).unwrap().as_str())
362 .unwrap_or(Value::Null);
363 let resolution_value = self.resolution_value.clone();
364 let comment_value = self.resolution_comment.clone();
365 version_update_op.add = Some(Some(version_json));
366 version_resolution_update_field.insert("resolution".to_string(), resolution_value);
367 version_resolution_comment_op.add = Some(Some(comment_value));
368 update_fields_hashmap.insert("fixVersions".to_string(), vec![version_update_op]);
369 transition_fields_hashmap
370 .insert("comment".to_string(), vec![version_resolution_comment_op]);
371 let issue_update_data = IssueUpdateDetails {
372 fields: None,
373 history_metadata: None,
374 properties: None,
375 transition: None,
376 update: Some(update_fields_hashmap),
377 };
378 let mut transition_result: String = "KO".to_string();
379 if !Vec::is_empty(&transitions) {
380 for transition in transitions {
381 let issue_transition_data = IssueUpdateDetails {
382 fields: Some(version_resolution_update_field.clone()),
383 history_metadata: None,
384 properties: None,
385 transition: Some(transition.clone().unwrap()),
386 update: Some(transition_fields_hashmap.clone()),
387 };
388 match do_transition(
389 &self.cfg,
390 issue.clone().as_str(),
391 issue_transition_data,
392 )
393 .await
394 {
395 Ok(_) => {
396 transition_result = "OK".to_string();
397 break;
398 }
399 Err(Error::Serde(e)) => {
400 if e.is_eof() {
401 transition_result = "OK".to_string();
402 break;
403 } else {
404 transition_result = "KO".to_string()
405 }
406 }
407 Err(_) => transition_result = "KO".to_string(),
408 }
409 }
410 }
411 let assign_result: String = match assign_issue(
412 &self.cfg,
413 issue.clone().as_str(),
414 user_data.clone().unwrap(),
415 )
416 .await
417 {
418 Ok(_) => "OK".to_string(),
419 Err(Error::Serde(e)) => {
420 if e.is_eof() {
421 "OK".to_string()
422 } else {
423 "KO".to_string()
424 }
425 }
426 Err(_) => "KO".to_string(),
427 };
428 let fixversion_result: String = match edit_issue(
429 &self.cfg,
430 issue.clone().as_str(),
431 issue_update_data,
432 Some(true),
433 None,
434 None,
435 Some(true),
436 None,
437 )
438 .await
439 {
440 Ok(_) => version.clone().name.unwrap_or("".to_string()),
441 Err(_) => "NO fixVersion set".to_string(),
442 };
443 transitioned_issue.push((
444 issue.clone(),
445 transition_result,
446 assign_result,
447 fixversion_result,
448 ));
449 }
450 }
451 Ok((
452 version,
453 if !transitioned_issue.is_empty() {
454 Some(transitioned_issue)
455 } else {
456 None
457 },
458 ))
459 }
460
461 /// This method gets a Jira version with the given parameters
462 /// and returns the version
463 /// If the version is not found, it returns an error
464 ///
465 /// # Arguments
466 ///
467 /// * `params` - A VersionCmdParams struct
468 ///
469 /// # Returns
470 ///
471 /// * A Result containing a Version struct or an Error<GetVersionError>
472 ///
473 /// # Examples
474 ///
475 /// ```no_run
476 /// use jira_v3_openapi::models::Version;
477 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdParams;
478 /// use jirust_cli::config::config_file::ConfigFile;
479 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdRunner;
480 /// use toml::Table;
481 ///
482 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
483 /// # tokio_test::block_on(async {
484 /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
485 /// let version_cmd_runner = VersionCmdRunner::new(&cfg_file);
486 /// let params = VersionCmdParams::new();
487 ///
488 /// let version = version_cmd_runner.get_jira_version(params).await?;
489 /// # Ok(())
490 /// # })
491 /// # }
492 /// ```
493 pub async fn get_jira_version(
494 &self,
495 params: VersionCmdParams,
496 ) -> Result<Version, Error<GetVersionError>> {
497 get_version(
498 &self.cfg,
499 params.version_id.expect("VersionID is mandatory!").as_str(),
500 None,
501 )
502 .await
503 }
504
505 /// This method lists Jira versions with the given parameters
506 /// and returns the versions
507 /// If there are no versions, it returns an empty vector
508 /// If the version is not found, it returns an error
509 /// If the version page size is given, it returns the paginated versions
510 /// Otherwise, it returns all versions
511 ///
512 /// # Arguments
513 ///
514 /// * `params` - A VersionCmdParams struct
515 ///
516 /// # Returns
517 ///
518 /// * A Result containing a vector of Version structs or a Box<dyn std::error::Error>
519 ///
520 /// # Examples
521 ///
522 /// ```no_run
523 /// use jira_v3_openapi::models::Version;
524 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdParams;
525 /// use jirust_cli::config::config_file::ConfigFile;
526 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdRunner;
527 /// use toml::Table;
528 ///
529 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
530 /// # tokio_test::block_on(async {
531 /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
532 /// let version_cmd_runner = VersionCmdRunner::new(&cfg_file);
533 /// let params = VersionCmdParams::new();
534 ///
535 /// let versions = version_cmd_runner.list_jira_versions(params).await?;
536 /// # Ok(())
537 /// # })
538 /// # }
539 /// ```
540 pub async fn list_jira_versions(
541 &self,
542 params: VersionCmdParams,
543 ) -> Result<Vec<Version>, Box<dyn std::error::Error>> {
544 if Option::is_some(¶ms.versions_page_size) {
545 match get_project_versions_paginated(
546 &self.cfg,
547 params.project.as_str(),
548 params.versions_page_offset,
549 params.versions_page_size,
550 None,
551 None,
552 None,
553 None,
554 )
555 .await?
556 .values
557 {
558 Some(values) => Ok(values),
559 None => Ok(vec![]),
560 }
561 } else {
562 Ok(get_project_versions(&self.cfg, params.project.as_str(), None).await?)
563 }
564 }
565
566 /// This method updates a Jira version with the given parameters
567 /// and returns the updated version
568 /// If the version is not found, it returns an error
569 /// If the version ID is not given, it returns an error
570 ///
571 /// # Arguments
572 ///
573 /// * `params` - A VersionCmdParams struct
574 ///
575 /// # Returns
576 ///
577 /// * A Result containing a Version struct or an Error<UpdateVersionError>
578 ///
579 /// # Examples
580 ///
581 /// ```no_run
582 /// use jira_v3_openapi::models::Version;
583 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdParams;
584 /// use jirust_cli::config::config_file::ConfigFile;
585 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdRunner;
586 /// use toml::Table;
587 ///
588 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
589 /// # tokio_test::block_on(async {
590 /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
591 /// let version_cmd_runner = VersionCmdRunner::new(&cfg_file);
592 /// let params = VersionCmdParams::new();
593 ///
594 /// let version = version_cmd_runner.update_jira_version(params).await?;
595 /// # Ok(())
596 /// # })
597 /// # }
598 /// ```
599 pub async fn update_jira_version(
600 &self,
601 params: VersionCmdParams,
602 ) -> Result<Version, Error<UpdateVersionError>> {
603 let release_date =
604 if Option::is_some(¶ms.version_released) && params.version_released.unwrap() {
605 if Option::is_some(¶ms.version_release_date) {
606 params.version_release_date
607 } else {
608 Some(Utc::now().format("%Y-%m-%d").to_string())
609 }
610 } else {
611 None
612 };
613 let version = Version {
614 id: Some(params.version_id.clone().expect("VersionID is mandatory!")),
615 name: params.version_name,
616 description: params.version_description,
617 start_date: params.version_start_date,
618 release_date,
619 archived: params.version_archived,
620 released: params.version_released,
621 ..Default::default()
622 };
623 update_version(
624 &self.cfg,
625 params.version_id.expect("VersionID is mandatory!").as_str(),
626 version,
627 )
628 .await
629 }
630
631 /// This method deletes a Jira version with the given parameters
632 /// and returns the status of the deletion
633 ///
634 /// # Arguments
635 ///
636 /// * `params` - A VersionCmdParams struct
637 ///
638 /// # Returns
639 ///
640 /// * A Result containing a serde_json::Value or an Error<DeleteAndReplaceVersionError>
641 ///
642 /// # Examples
643 ///
644 /// ```no_run
645 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdParams;
646 /// use jirust_cli::config::config_file::ConfigFile;
647 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdRunner;
648 /// use toml::Table;
649 ///
650 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
651 /// # tokio_test::block_on(async {
652 /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
653 /// let version_cmd_runner = VersionCmdRunner::new(&cfg_file);
654 /// let params = VersionCmdParams::new();
655 ///
656 /// let status = version_cmd_runner.delete_jira_version(params).await?;
657 /// # Ok(())
658 /// # })
659 /// # }
660 /// ```
661 pub async fn delete_jira_version(
662 &self,
663 params: VersionCmdParams,
664 ) -> Result<serde_json::Value, Error<DeleteAndReplaceVersionError>> {
665 match delete_and_replace_version(
666 &self.cfg,
667 params.version_id.expect("VersionID is mandatory!").as_str(),
668 DeleteAndReplaceVersionBean::new(),
669 )
670 .await
671 {
672 Ok(_) => Ok(serde_json::json!({"status": "success"})),
673 Err(e) => match e {
674 Error::Serde(_) => Ok(
675 serde_json::json!({"status": "success", "warning": "Version was deleted, some issues in deserializing response!"}),
676 ),
677 _ => Err(e),
678 },
679 }
680 }
681
682 /// This method retrieves the related work for a given version.
683 ///
684 /// # Arguments
685 ///
686 /// * `params` - The parameters for the command.
687 ///
688 /// # Returns
689 ///
690 /// A `Result` containing a vector of `VersionRelatedWork` or an error.
691 ///
692 /// # Examples
693 ///
694 /// ```no_run
695 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdParams;
696 /// use jirust_cli::config::config_file::ConfigFile;
697 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdRunner;
698 /// use toml::Table;
699 ///
700 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
701 /// # tokio_test::block_on(async {
702 /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
703 /// let version_cmd_runner = VersionCmdRunner::new(&cfg_file);
704 /// let params = VersionCmdParams::new();
705 ///
706 /// let items = version_cmd_runner.get_jira_version_related_work(params).await?;
707 /// # Ok(())
708 /// # })
709 /// # }
710 /// ```
711 pub async fn get_jira_version_related_work(
712 &self,
713 params: VersionCmdParams,
714 ) -> Result<Vec<VersionRelatedWork>, Error<GetRelatedWorkError>> {
715 get_related_work(
716 &self.cfg,
717 params.version_id.expect("VersionID is mandatory!").as_str(),
718 )
719 .await
720 }
721
722 /// Manage version related issues helper function
723 /// Use FuturesUnordered to manage multiple operation concurrently
724 ///
725 /// # Arguments
726 /// * `issue` - The issue to manage
727 /// * `user_data` - The user data
728 /// * `version` - The version
729 ///
730 /// # Returns
731 /// A Result containing a tuple with the issue key, status, resolution, and fix version or an error
732 ///
733 /// # Example
734 /// ```ignore
735 /// use jira_v3_openapi::models::{ User, Version };
736 /// use jirust_cli::config::config_file::ConfigFile;
737 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdRunner;
738 /// use toml::Table;
739 ///
740 /// let cfg_file = ConfigFile::new("dXNlcm5hbWU6YXBpX2tleQ==".to_string(), "jira_url".to_string(), "standard_resolution".to_string(), "standard_resolution_comment".to_string(), Table::new());
741 /// let version_cmd_runner = VersionCmdRunner::new(&cfg_file);
742 /// let issue_key = "ABC-123";
743 /// let user_data = Some(User::default());
744 /// let version = Version::default();
745 /// let result = self.manage_version_related_issues(issue_key, &user_data, &version).await;
746 /// ```
747 ///
748 #[cfg(any(windows, unix))]
749 async fn manage_version_related_issues(
750 &self,
751 issue: String,
752 user_data: &Option<User>,
753 version: &Version,
754 ) -> Result<(String, String, String, String), Box<dyn std::error::Error>> {
755 let all_transitions: Vec<IssueTransition> = match get_transitions(
756 &self.cfg,
757 issue.clone().as_str(),
758 None,
759 None,
760 None,
761 Some(false),
762 None,
763 )
764 .await
765 {
766 Ok(transitions) => transitions.transitions.unwrap_or_default(),
767 Err(_) => {
768 return Ok((
769 issue,
770 "KO".to_string(),
771 "KO".to_string(),
772 "NO fixVersion set".to_string(),
773 ));
774 }
775 };
776 let transition_names: Vec<String> = self
777 .resolution_transition_name
778 .clone()
779 .expect("Transition name is required and must be set in the config file");
780 let resolve_transitions: Vec<IssueTransition> = all_transitions
781 .into_iter()
782 .filter(|t| transition_names.contains(&t.name.clone().unwrap_or("".to_string())))
783 .collect();
784 let transition_ids = resolve_transitions
785 .into_iter()
786 .map(|t| t.id.clone().unwrap_or("".to_string()))
787 .collect::<Vec<String>>();
788 let transitions = transition_ids
789 .into_iter()
790 .map(|id| {
791 Some(IssueTransition {
792 id: Some(id),
793 ..Default::default()
794 })
795 })
796 .collect::<Vec<Option<IssueTransition>>>();
797 let mut update_fields_hashmap: HashMap<String, Vec<FieldUpdateOperation>> = HashMap::new();
798 let mut transition_fields_hashmap: HashMap<String, Vec<FieldUpdateOperation>> =
799 HashMap::new();
800 let mut version_update_op = FieldUpdateOperation::new();
801 let mut version_resolution_update_field = HashMap::new();
802 let mut version_resolution_comment_op = FieldUpdateOperation::new();
803 let version_json: Value =
804 serde_json::from_str(serde_json::to_string(&version).unwrap().as_str())
805 .unwrap_or(Value::Null);
806 let resolution_value = self.resolution_value.clone();
807 let comment_value = self.resolution_comment.clone();
808 version_update_op.add = Some(Some(version_json));
809 version_resolution_update_field.insert("resolution".to_string(), resolution_value);
810 version_resolution_comment_op.add = Some(Some(comment_value));
811 update_fields_hashmap.insert("fixVersions".to_string(), vec![version_update_op]);
812 transition_fields_hashmap
813 .insert("comment".to_string(), vec![version_resolution_comment_op]);
814 let issue_update_data = IssueUpdateDetails {
815 fields: None,
816 history_metadata: None,
817 properties: None,
818 transition: None,
819 update: Some(update_fields_hashmap),
820 };
821 let mut transition_result: String = "KO".to_string();
822 if !Vec::is_empty(&transitions) {
823 for transition in transitions {
824 let issue_transition_data = IssueUpdateDetails {
825 fields: Some(version_resolution_update_field.clone()),
826 history_metadata: None,
827 properties: None,
828 transition: Some(transition.clone().unwrap()),
829 update: Some(transition_fields_hashmap.clone()),
830 };
831 match do_transition(&self.cfg, issue.clone().as_str(), issue_transition_data).await
832 {
833 Ok(_) => {
834 transition_result = "OK".to_string();
835 break;
836 }
837 Err(Error::Serde(e)) => {
838 if e.is_eof() {
839 transition_result = "OK".to_string();
840 break;
841 } else {
842 transition_result = "KO".to_string()
843 }
844 }
845 Err(_) => transition_result = "KO".to_string(),
846 }
847 }
848 }
849 let assign_result: String = match assign_issue(
850 &self.cfg,
851 issue.clone().as_str(),
852 user_data.clone().unwrap(),
853 )
854 .await
855 {
856 Ok(_) => "OK".to_string(),
857 Err(Error::Serde(e)) => {
858 if e.is_eof() {
859 "OK".to_string()
860 } else {
861 "KO".to_string()
862 }
863 }
864 Err(_) => "KO".to_string(),
865 };
866 let fixversion_result: String = match edit_issue(
867 &self.cfg,
868 issue.clone().as_str(),
869 issue_update_data,
870 Some(true),
871 None,
872 None,
873 Some(true),
874 None,
875 )
876 .await
877 {
878 Ok(_) => version.clone().name.unwrap_or("".to_string()),
879 Err(_) => "NO fixVersion set".to_string(),
880 };
881 Ok((issue, transition_result, assign_result, fixversion_result))
882 }
883}
884
885/// This struct defines the parameters for the Version commands
886///
887/// # Fields
888///
889/// * `project` - The project key, always **required**.
890/// * `project_id` - The project ID, optional.
891/// * `version_name` - The version name, optional.
892/// * `version_id` - The version ID, **required** for archive, delete, release and update.
893/// * `version_description` - The version description, optional.
894/// * `version_start_date` - The version start date, optional (default: today on create command).
895/// * `version_release_date` - The version release date, optional (default: today on release command).
896/// * `version_archived` - The version archived status, optional.
897/// * `version_released` - The version released status, optional.
898/// * `changelog_file` - The changelog file path, to be used for automatic description generation (changelog-based), optional: if set the script detects automatically the first tagged block in the changelog and use it as description
899/// * `resolve_issues` - The flag to resolve issues in the version, optional.
900/// * `versions_page_size` - The page size for the version, optional.
901/// * `versions_page_offset` - The page offset for the version, optional.
902pub struct VersionCmdParams {
903 pub project: String,
904 pub project_id: Option<i64>,
905 pub version_name: Option<String>,
906 pub version_id: Option<String>,
907 pub version_description: Option<String>,
908 pub version_start_date: Option<String>,
909 pub version_release_date: Option<String>,
910 pub version_archived: Option<bool>,
911 pub version_released: Option<bool>,
912 pub changelog_file: Option<String>,
913 pub transition_issues: Option<bool>,
914 pub transition_assignee: Option<String>,
915 pub versions_page_size: Option<i32>,
916 pub versions_page_offset: Option<i64>,
917}
918
919/// Implementation of the VersionCmdParams struct
920///
921/// # Methods
922///
923/// * `new` - returns a new VersionCmdParams struct
924/// * `merge_args` - merges the current version with the optional arguments
925/// * `mark_released` - marks the version as released
926/// * `mark_archived` - marks the version as archived
927impl VersionCmdParams {
928 /// This method returns a new VersionCmdParams struct
929 ///
930 /// # Returns
931 ///
932 /// * A VersionCmdParams struct
933 ///
934 /// # Examples
935 ///
936 /// ```
937 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdParams;
938 ///
939 /// let params = VersionCmdParams::new();
940 /// ```
941 pub fn new() -> VersionCmdParams {
942 VersionCmdParams {
943 project: "".to_string(),
944 project_id: None,
945 version_name: None,
946 version_id: None,
947 version_description: None,
948 version_start_date: None,
949 version_release_date: None,
950 version_archived: None,
951 version_released: None,
952 changelog_file: None,
953 transition_issues: None,
954 transition_assignee: None,
955 versions_page_size: None,
956 versions_page_offset: None,
957 }
958 }
959
960 /// This method merges the current version with the optional arguments
961 /// and returns a VersionCmdParams struct
962 /// If the optional arguments are not given, it uses the current version values
963 ///
964 /// # Arguments
965 ///
966 /// * `current_version` - A Version struct
967 /// * `opt_args` - An Option<&VersionArgs> struct
968 ///
969 /// # Returns
970 ///
971 /// * A VersionCmdParams struct
972 ///
973 /// # Examples
974 ///
975 /// ```
976 /// use jira_v3_openapi::models::Version;
977 /// use jirust_cli::args::commands::{VersionArgs, VersionActionValues, PaginationArgs, OutputArgs};
978 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdParams;
979 ///
980 /// let mut current_version: Version = Version::new();
981 /// current_version.id = Some("12345".to_string());
982 /// current_version.project_id = Some(9876);
983 /// current_version.project = Some("TEST_PROJECT".to_string());
984 /// current_version.name = Some("v1.0".to_string());
985 /// current_version.description = Some("This is the first version".to_string());
986 ///
987 /// let opt_args = VersionArgs {
988 /// version_act: VersionActionValues::List,
989 /// project_key: "project_key".to_string(),
990 /// project_id: None,
991 /// version_id: Some("97531".to_string()),
992 /// version_name: Some("version_name".to_string()),
993 /// version_description: Some("version_description".to_string()),
994 /// version_start_date: None,
995 /// version_release_date: None,
996 /// version_archived: None,
997 /// version_released: Some(true),
998 /// changelog_file: None,
999 /// pagination: PaginationArgs { page_size: None, page_offset: None },
1000 /// output: OutputArgs { output_format: None, output_type: None },
1001 /// transition_issues: None,
1002 /// transition_assignee: None,
1003 /// };
1004 ///
1005 /// let params = VersionCmdParams::merge_args(current_version, Some(&opt_args));
1006 ///
1007 /// assert_eq!(params.project, "TEST_PROJECT".to_string());
1008 /// assert_eq!(params.project_id, Some(9876));
1009 /// assert_eq!(params.version_id, Some("12345".to_string()));
1010 /// assert_eq!(params.version_name, Some("version_name".to_string()));
1011 /// assert_eq!(params.version_description, Some("version_description".to_string()));
1012 /// assert_eq!(params.version_released, Some(true));
1013 /// ```
1014 pub fn merge_args(
1015 current_version: Version,
1016 opt_args: Option<&VersionArgs>,
1017 ) -> VersionCmdParams {
1018 match opt_args {
1019 Some(args) => VersionCmdParams {
1020 project: current_version.project.clone().unwrap_or("".to_string()),
1021 project_id: current_version.project_id,
1022 version_id: current_version.id,
1023 version_name: if Option::is_some(&args.version_name) {
1024 args.version_name.clone()
1025 } else {
1026 current_version.name
1027 },
1028 version_description: if Option::is_some(&args.version_description) {
1029 args.version_description.clone()
1030 } else {
1031 current_version.description
1032 },
1033 version_start_date: if Option::is_some(&args.version_start_date) {
1034 args.version_start_date.clone()
1035 } else {
1036 current_version.start_date
1037 },
1038 version_release_date: if Option::is_some(&args.version_release_date) {
1039 args.version_release_date.clone()
1040 } else {
1041 current_version.release_date
1042 },
1043 version_archived: if Option::is_some(&args.version_archived) {
1044 args.version_archived
1045 } else {
1046 current_version.archived
1047 },
1048 version_released: if Option::is_some(&args.version_released) {
1049 args.version_released
1050 } else {
1051 current_version.released
1052 },
1053 changelog_file: None,
1054 transition_issues: None,
1055 transition_assignee: None,
1056 versions_page_size: None,
1057 versions_page_offset: None,
1058 },
1059 None => VersionCmdParams {
1060 project: current_version.project.clone().unwrap_or("".to_string()),
1061 project_id: current_version.project_id,
1062 version_id: current_version.id,
1063 version_name: current_version.name,
1064 version_description: current_version.description,
1065 version_start_date: current_version.start_date,
1066 version_release_date: current_version.release_date,
1067 version_archived: current_version.archived,
1068 version_released: current_version.released,
1069 changelog_file: None,
1070 transition_issues: None,
1071 transition_assignee: None,
1072 versions_page_size: None,
1073 versions_page_offset: None,
1074 },
1075 }
1076 }
1077
1078 /// This method marks the version as released
1079 /// and returns a VersionCmdParams struct
1080 /// It sets the version_released and version_release_date fields
1081 /// with the current date
1082 ///
1083 /// # Arguments
1084 ///
1085 /// * `version` - A Version struct
1086 ///
1087 /// # Returns
1088 ///
1089 /// * A VersionCmdParams struct
1090 ///
1091 /// # Examples
1092 ///
1093 /// ```
1094 /// use jira_v3_openapi::models::Version;
1095 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdParams;
1096 ///
1097 /// let mut version: Version = Version::new();
1098 /// version.id = Some("12345".to_string());
1099 /// version.project_id = Some(9876);
1100 /// version.project = Some("TEST_PROJECT".to_string());
1101 /// version.name = Some("v1.0".to_string());
1102 /// version.description = Some("This is the first version".to_string());
1103 ///
1104 /// assert_eq!(version.released, None);
1105 ///
1106 /// let params = VersionCmdParams::mark_released(version);
1107 ///
1108 /// assert_eq!(params.version_released, Some(true));
1109 /// ```
1110 pub fn mark_released(version: Version) -> VersionCmdParams {
1111 let mut version_to_release = Self::merge_args(version, None);
1112 version_to_release.version_released = Some(true);
1113 version_to_release.version_release_date = Some(Utc::now().format("%Y-%m-%d").to_string());
1114 version_to_release
1115 }
1116
1117 /// This method marks the version as archived
1118 /// and returns a VersionCmdParams struct
1119 ///
1120 /// # Arguments
1121 ///
1122 /// * `version` - A Version struct
1123 ///
1124 /// # Returns
1125 ///
1126 /// * A VersionCmdParams struct
1127 ///
1128 /// # Examples
1129 ///
1130 /// ```
1131 /// use jira_v3_openapi::models::Version;
1132 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdParams;
1133 ///
1134 /// let mut version: Version = Version::new();
1135 /// version.id = Some("12345".to_string());
1136 /// version.project_id = Some(9876);
1137 /// version.project = Some("TEST_PROJECT".to_string());
1138 /// version.name = Some("v1.0".to_string());
1139 /// version.description = Some("This is the first version".to_string());
1140 ///
1141 /// assert_eq!(version.archived, None);
1142 ///
1143 /// let params = VersionCmdParams::mark_archived(version);
1144 ///
1145 /// assert_eq!(params.version_archived, Some(true));
1146 /// ```
1147 ///
1148 pub fn mark_archived(version: Version) -> VersionCmdParams {
1149 let mut version_to_archive = Self::merge_args(version, None);
1150 version_to_archive.version_archived = Some(true);
1151 version_to_archive
1152 }
1153}
1154
1155/// Implementation of the From trait for the VersionArgs struct
1156/// This implementation allows the conversion of a VersionArgs struct to a VersionCmdParams struct.
1157impl From<&VersionArgs> for VersionCmdParams {
1158 /// This method converts the VersionArgs struct to a VersionCmdParams struct
1159 /// and returns a VersionCmdParams struct
1160 ///
1161 /// # Arguments
1162 ///
1163 /// * `args` - A VersionArgs struct
1164 ///
1165 /// # Returns
1166 ///
1167 /// * A VersionCmdParams struct
1168 ///
1169 /// # Examples
1170 ///
1171 /// ```
1172 /// use jirust_cli::args::commands::{VersionActionValues, VersionArgs, PaginationArgs, OutputArgs};
1173 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdParams;
1174 ///
1175 /// let version_args = VersionArgs {
1176 /// version_act: VersionActionValues::List,
1177 /// project_key: "project_key".to_string(),
1178 /// project_id: None,
1179 /// version_id: None,
1180 /// version_name: Some("version_name".to_string()),
1181 /// version_description: Some("version_description".to_string()),
1182 /// version_start_date: None,
1183 /// version_release_date: None,
1184 /// version_archived: None,
1185 /// version_released: None,
1186 /// changelog_file: None,
1187 /// pagination: PaginationArgs { page_size: Some(10), page_offset: Some(0) },
1188 /// output: OutputArgs { output_format: None, output_type: None },
1189 /// transition_issues: None,
1190 /// transition_assignee: None,
1191 /// };
1192 ///
1193 /// let params = VersionCmdParams::from(&version_args);
1194 ///
1195 /// assert_eq!(params.project, "project_key".to_string());
1196 /// assert_eq!(params.version_name, Some("version_name".to_string()));
1197 /// assert_eq!(params.version_description, Some("version_description".to_string()));
1198 /// assert_eq!(params.versions_page_size, Some(10));
1199 /// assert_eq!(params.versions_page_offset, Some(0));
1200 /// ```
1201 fn from(args: &VersionArgs) -> Self {
1202 VersionCmdParams {
1203 project: args.project_key.clone(),
1204 project_id: args.project_id,
1205 version_name: args.version_name.clone(),
1206 version_id: args.version_id.clone(),
1207 version_description: args.version_description.clone(),
1208 version_start_date: Some(
1209 args.version_start_date
1210 .clone()
1211 .unwrap_or(Utc::now().format("%Y-%m-%d").to_string()),
1212 ),
1213 version_release_date: args.version_release_date.clone(),
1214 version_archived: args.version_archived,
1215 version_released: args.version_released,
1216 changelog_file: args.changelog_file.clone(),
1217 transition_issues: args.transition_issues,
1218 transition_assignee: args.transition_assignee.clone(),
1219 versions_page_size: args.pagination.page_size,
1220 versions_page_offset: args.pagination.page_offset,
1221 }
1222 }
1223}
1224
1225/// Implementation of the Default trait for the VersionCmdParams struct
1226impl Default for VersionCmdParams {
1227 /// This method returns a VersionCmdParams struct with default values
1228 /// and returns a VersionCmdParams struct
1229 ///
1230 /// # Returns
1231 ///
1232 /// * A VersionCmdParams struct initialized with default values
1233 ///
1234 /// # Examples
1235 ///
1236 /// ```
1237 /// use jirust_cli::runners::jira_cmd_runners::version_cmd_runner::VersionCmdParams;
1238 ///
1239 /// let params = VersionCmdParams::default();
1240 ///
1241 /// assert_eq!(params.project, "".to_string());
1242 /// assert_eq!(params.project_id, None);
1243 /// assert_eq!(params.version_name, None);
1244 /// assert_eq!(params.version_id, None);
1245 /// assert_eq!(params.version_description, None);
1246 /// assert_eq!(params.version_start_date, None);
1247 /// assert_eq!(params.version_release_date, None);
1248 /// assert_eq!(params.version_archived, None);
1249 /// assert_eq!(params.version_released, None);
1250 /// assert_eq!(params.changelog_file, None);
1251 /// assert_eq!(params.transition_issues, None);
1252 /// assert_eq!(params.transition_assignee, None);
1253 /// assert_eq!(params.versions_page_size, None);
1254 /// assert_eq!(params.versions_page_offset, None);
1255 /// ```
1256 fn default() -> Self {
1257 VersionCmdParams::new()
1258 }
1259}