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(&params.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(&params.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(&params.transition_issues) && params.transition_issues.unwrap() {
144                resolved_issues = changelog_extractor
145                    .extract_issues_from_changelog(
146                        &version_description.clone().unwrap(),
147                        &params.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(&params.version_released) && params.version_released.unwrap() {
156                if Option::is_some(&params.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(&params.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(&params.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(&params.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(&params.transition_issues) && params.transition_issues.unwrap() {
272                resolved_issues = changelog_extractor
273                    .extract_issues_from_changelog(
274                        &version_description.clone().unwrap(),
275                        &params.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(&params.version_released) && params.version_released.unwrap() {
284                if Option::is_some(&params.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(&params.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(&params.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(&params.version_released) && params.version_released.unwrap() {
605                if Option::is_some(&params.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}