PageRenderTime 46ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/Atlassian.Jira/Issue.cs

https://bitbucket.org/yyo/atlassian.net-sdk-v2.0
C# | 807 lines | 565 code | 98 blank | 144 comment | 76 complexity | 5fd58759928e808d07cf895fe50e68d1 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Reflection;
  6. using System.IO;
  7. using System.Collections;
  8. using System.Collections.ObjectModel;
  9. using System.Dynamic;
  10. using Atlassian.Jira.Remote;
  11. using Atlassian.Jira.Linq;
  12. using Newtonsoft.Json.Linq;
  13. namespace Atlassian.Jira
  14. {
  15. /// <summary>
  16. /// A JIRA issue
  17. /// </summary>
  18. public class Issue : IRemoteIssueFieldProvider
  19. {
  20. private readonly Jira _jira;
  21. private readonly string _parentIssueKey;
  22. private ComparableString _key;
  23. private string _project;
  24. private RemoteIssue _originalIssue;
  25. private DateTime? _createDate;
  26. private DateTime? _updateDate;
  27. private DateTime? _dueDate;
  28. private ProjectVersionCollection _affectsVersions = null;
  29. private ProjectVersionCollection _fixVersions = null;
  30. private ProjectComponentCollection _components = null;
  31. private CustomFieldCollection _customFields = null;
  32. /// <summary>
  33. /// Create an issue to be saved into JIRA
  34. /// </summary>
  35. /// <param name="jira">The JIRA server where the issue will be saved at.</param>
  36. /// <param name="projectKey">The project that the issue will be saved at.</param>
  37. /// <param name="parentIssueKey">An optional parent issue key</param>
  38. public Issue(Jira jira, string projectKey, string parentIssueKey = null)
  39. : this(jira, new RemoteIssue() { project = projectKey }, parentIssueKey)
  40. {
  41. }
  42. /// <summary>
  43. /// Create an issue from a JSON represantation
  44. /// </summary>
  45. /// <param name="jira">The Jira object that retrieved the issue</param>
  46. /// <param name="json">The JSON representation of this issue</param>
  47. public static Issue FromJson(Jira jira, string json)
  48. {
  49. var jsonIssue = JObject.Parse(json);
  50. var fields = jsonIssue["fields"] as JObject;
  51. var remoteIssue = new RemoteIssue()
  52. {
  53. key = (string)jsonIssue["key"],
  54. summary = (string)fields["summary"],
  55. assignee = fields["assignee"].Type == JTokenType.Null ? null : (string)fields["assignee"]["name"],
  56. created = fields["created"].Type == JTokenType.Null ? null : (DateTime?)fields["created"],
  57. duedate = fields["duedate"].Type == JTokenType.Null ? null : (DateTime?)fields["duedate"],
  58. description = fields["description"].Type == JTokenType.Null ? null : (string)fields["description"],
  59. environment = fields["environment"].Type == JTokenType.Null ? null : (string)fields["environment"],
  60. priority = fields["priority"].Type == JTokenType.Null ? null : (string)fields["priority"]["id"],
  61. project = (string) fields["project"]["key"],
  62. reporter = fields["reporter"].Type == JTokenType.Null ? null : (string)fields["reporter"]["name"],
  63. resolution = fields["resolution"].Type == JTokenType.Null? null: (string) fields["resolution"]["id"],
  64. status = fields["status"].Type == JTokenType.Null? null: (string) fields["status"]["id"],
  65. type = fields["issuetype"].Type == JTokenType.Null? null: (string) fields["issuetype"]["id"],
  66. updated = fields["updated"].Type == JTokenType.Null ? null : (DateTime?)fields["updated"],
  67. votes = fields["votes"].Type == JTokenType.Null ? null : (long?)fields["votes"]["votes"]
  68. };
  69. if (fields["versions"].Type != JTokenType.Null)
  70. {
  71. remoteIssue.affectsVersions = (from v in (JArray)fields["versions"]
  72. select new RemoteVersion() { id = (string)v["id"], name = (string)v["name"] }).ToArray();
  73. }
  74. if (fields["fixVersions"].Type != JTokenType.Null)
  75. {
  76. remoteIssue.fixVersions = (from v in (JArray)fields["fixVersions"]
  77. select new RemoteVersion() { id = (string)v["id"], name = (string)v["name"] }).ToArray();
  78. }
  79. if (fields["components"].Type != JTokenType.Null)
  80. {
  81. remoteIssue.components = (from v in (JArray)fields["components"]
  82. select new RemoteComponent() { id = (string)v["id"], name = (string)v["name"] }).ToArray();
  83. }
  84. var customFields = new List<RemoteCustomFieldValue>();
  85. foreach (var p in fields)
  86. {
  87. if (p.Key.StartsWith("customfield", StringComparison.InvariantCulture))
  88. {
  89. customFields.Add(new RemoteCustomFieldValue() { customfieldId = p.Key, values = new string[] { (string)p.Value }});
  90. }
  91. }
  92. remoteIssue.customFieldValues = customFields.Count > 0? customFields.ToArray(): null;
  93. return new Issue(jira, remoteIssue);
  94. }
  95. internal Issue(Jira jira, RemoteIssue remoteIssue, string parentIssueKey = null)
  96. {
  97. _jira = jira;
  98. _parentIssueKey = parentIssueKey;
  99. Initialize(remoteIssue);
  100. }
  101. private void Initialize(RemoteIssue remoteIssue)
  102. {
  103. _originalIssue = remoteIssue;
  104. _project = remoteIssue.project;
  105. _key = remoteIssue.key;
  106. _createDate = remoteIssue.created;
  107. _dueDate = remoteIssue.duedate;
  108. _updateDate = remoteIssue.updated;
  109. Assignee = remoteIssue.assignee;
  110. Description = remoteIssue.description;
  111. Environment = remoteIssue.environment;
  112. Reporter = remoteIssue.reporter;
  113. Summary = remoteIssue.summary;
  114. Votes = remoteIssue.votes;
  115. // named entities
  116. Status = String.IsNullOrEmpty(remoteIssue.status) ? null : new IssueStatus(_jira, remoteIssue.status);
  117. Priority = String.IsNullOrEmpty(remoteIssue.priority) ? null : new IssuePriority(_jira, remoteIssue.priority);
  118. Resolution = String.IsNullOrEmpty(remoteIssue.resolution) ? null : new IssueResolution(_jira, remoteIssue.resolution);
  119. Type = String.IsNullOrEmpty(remoteIssue.type)? null: new IssueType(_jira, remoteIssue.type);
  120. // collections
  121. _affectsVersions = _originalIssue.affectsVersions == null ? new ProjectVersionCollection("versions", _jira, Project)
  122. : new ProjectVersionCollection("versions", _jira, Project, _originalIssue.affectsVersions.Select(v => new ProjectVersion(v)).ToList());
  123. _fixVersions = _originalIssue.fixVersions == null ? new ProjectVersionCollection("fixVersions", _jira, Project)
  124. : new ProjectVersionCollection("fixVersions", _jira, Project, _originalIssue.fixVersions.Select(v => new ProjectVersion(v)).ToList());
  125. _components = _originalIssue.components == null ? new ProjectComponentCollection("components", _jira, Project)
  126. : new ProjectComponentCollection("components", _jira, Project, _originalIssue.components.Select(c => new ProjectComponent(c)).ToList());
  127. _customFields = _originalIssue.customFieldValues == null ? new CustomFieldCollection(_jira, Project)
  128. : new CustomFieldCollection(_jira, Project, _originalIssue.customFieldValues.Select(f => new CustomField(f.customfieldId, Project, _jira) { Values = f.values }).ToList());
  129. }
  130. /// <summary>
  131. /// The JIRA server that created this issue
  132. /// </summary>
  133. public Jira Jira
  134. {
  135. get
  136. {
  137. return _jira;
  138. }
  139. }
  140. /// <summary>
  141. /// Brief one-line summary of the issue
  142. /// </summary>
  143. [JqlContainsEquality]
  144. public string Summary { get; set; }
  145. /// <summary>
  146. /// Detailed description of the issue
  147. /// </summary>
  148. [JqlContainsEquality]
  149. public string Description { get; set; }
  150. /// <summary>
  151. /// Hardware or software environment to which the issue relates
  152. /// </summary>
  153. [JqlContainsEquality]
  154. public string Environment { get; set; }
  155. /// <summary>
  156. /// Person to whom the issue is currently assigned
  157. /// </summary>
  158. public string Assignee { get; set; }
  159. /// <summary>
  160. /// Unique identifier for this issue
  161. /// </summary>
  162. public ComparableString Key
  163. {
  164. get
  165. {
  166. return _key;
  167. }
  168. }
  169. /// <summary>
  170. /// Importance of the issue in relation to other issues
  171. /// </summary>
  172. public IssuePriority Priority { get; set; }
  173. /// <summary>
  174. /// Parent project to which the issue belongs
  175. /// </summary>
  176. public string Project
  177. {
  178. get
  179. {
  180. return _project;
  181. }
  182. }
  183. /// <summary>
  184. /// Person who entered the issue into the system
  185. /// </summary>
  186. public string Reporter { get; set; }
  187. /// <summary>
  188. /// Record of the issue's resolution, if the issue has been resolved or closed
  189. /// </summary>
  190. public IssueResolution Resolution { get; set; }
  191. /// <summary>
  192. /// The stage the issue is currently at in its lifecycle.
  193. /// </summary>
  194. public IssueStatus Status { get; set; }
  195. /// <summary>
  196. /// The type of the issue
  197. /// </summary>
  198. [RemoteFieldName("issuetype")]
  199. public IssueType Type { get; set; }
  200. /// <summary>
  201. /// Number of votes the issue has
  202. /// </summary>
  203. public long? Votes { get; set; }
  204. /// <summary>
  205. /// Time and date on which this issue was entered into JIRA
  206. /// </summary>
  207. public DateTime? Created
  208. {
  209. get
  210. {
  211. return _createDate;
  212. }
  213. }
  214. /// <summary>
  215. /// Date by which this issue is scheduled to be completed
  216. /// </summary>
  217. public DateTime? DueDate
  218. {
  219. get
  220. {
  221. return _dueDate;
  222. }
  223. set
  224. {
  225. _dueDate = value;
  226. }
  227. }
  228. /// <summary>
  229. /// Time and date on which this issue was last edited
  230. /// </summary>
  231. public DateTime? Updated
  232. {
  233. get
  234. {
  235. return _updateDate;
  236. }
  237. }
  238. /// <summary>
  239. /// The components associated with this issue
  240. /// </summary>
  241. [JqlFieldName("component")]
  242. public ProjectComponentCollection Components
  243. {
  244. get
  245. {
  246. return _components;
  247. }
  248. }
  249. /// <summary>
  250. /// The versions that are affected by this issue
  251. /// </summary>
  252. [JqlFieldName("AffectedVersion")]
  253. public ProjectVersionCollection AffectsVersions
  254. {
  255. get
  256. {
  257. return _affectsVersions;
  258. }
  259. }
  260. /// <summary>
  261. /// The versions in which this issue is fixed
  262. /// </summary>
  263. [JqlFieldName("FixVersion")]
  264. public ProjectVersionCollection FixVersions
  265. {
  266. get
  267. {
  268. return _fixVersions;
  269. }
  270. }
  271. /// <summary>
  272. /// The custom fields associated with this issue
  273. /// </summary>
  274. public CustomFieldCollection CustomFields
  275. {
  276. get
  277. {
  278. return _customFields;
  279. }
  280. }
  281. /// <summary>
  282. /// Gets or sets the value of a custom field
  283. /// </summary>
  284. /// <param name="customFieldName">Custom field name</param>
  285. /// <returns>Value of the custom field</returns>
  286. public ComparableString this[string customFieldName]
  287. {
  288. get
  289. {
  290. var customField = _customFields[customFieldName];
  291. if(customField != null && customField.Values != null && customField.Values.Count() > 0)
  292. {
  293. return customField.Values[0];
  294. }
  295. return null;
  296. }
  297. set
  298. {
  299. var customField = _customFields[customFieldName];
  300. if (customField != null)
  301. {
  302. customField.Values = new string[] { value.Value };
  303. }
  304. else
  305. {
  306. _customFields.Add(customFieldName, new string[] { value.Value });
  307. }
  308. }
  309. }
  310. /// <summary>
  311. /// Saves field changes to server
  312. /// </summary>
  313. public void SaveChanges()
  314. {
  315. if (String.IsNullOrEmpty(_originalIssue.key))
  316. {
  317. var remoteIssue = this.ToRemote();
  318. _jira.WithToken(token =>
  319. {
  320. if (String.IsNullOrEmpty(_parentIssueKey))
  321. {
  322. remoteIssue = _jira.RemoteService.CreateIssue(token, remoteIssue);
  323. }
  324. else
  325. {
  326. remoteIssue = _jira.RemoteService.CreateIssueWithParent(token, remoteIssue, _parentIssueKey);
  327. }
  328. });
  329. Initialize(remoteIssue);
  330. }
  331. else
  332. {
  333. UpdateRemoteFields(((IRemoteIssueFieldProvider)this).GetRemoteFields());
  334. }
  335. }
  336. /// <summary>
  337. /// Transition an issue through a workflow
  338. /// </summary>
  339. /// <param name="actionName">The workflow action to transition to</param>
  340. public void WorkflowTransition(string actionName)
  341. {
  342. if (String.IsNullOrEmpty(_originalIssue.key))
  343. {
  344. throw new InvalidOperationException("Unable to execute workflow transition, issue has not been created.");
  345. }
  346. var action = this.GetAvailableActions().FirstOrDefault(a => a.Name.Equals(actionName, StringComparison.OrdinalIgnoreCase));
  347. if (action == null)
  348. {
  349. throw new InvalidOperationException(String.Format("Worflow action with name '{0}' not found.", actionName));
  350. }
  351. _jira.WithToken(token =>
  352. {
  353. var remoteIssue = _jira.RemoteService.ProgressWorkflowAction(
  354. token,
  355. _originalIssue.key,
  356. action.Id,
  357. ((IRemoteIssueFieldProvider)this).GetRemoteFields());
  358. Initialize(remoteIssue);
  359. });
  360. }
  361. private void UpdateRemoteFields(RemoteFieldValue[] remoteFields)
  362. {
  363. var remoteIssue = _jira.WithToken(token =>
  364. {
  365. return _jira.RemoteService.UpdateIssue(token, this.Key.Value, remoteFields);
  366. });
  367. Initialize(remoteIssue);
  368. }
  369. /// <summary>
  370. /// Retrieve attachment metadata from server for this issue
  371. /// </summary>
  372. public ReadOnlyCollection<Attachment> GetAttachments()
  373. {
  374. if (String.IsNullOrEmpty(_originalIssue.key))
  375. {
  376. throw new InvalidOperationException("Unable to retrieve attachments from server, issue has not been created.");
  377. }
  378. return _jira.WithToken(token =>
  379. {
  380. return _jira.RemoteService.GetAttachmentsFromIssue(token, _originalIssue.key)
  381. .Select(a => new Attachment(_jira, new WebClientWrapper(), a)).ToList().AsReadOnly();
  382. });
  383. }
  384. /// <summary>
  385. /// Add one or more attachments to this issue
  386. /// </summary>
  387. /// <param name="filePaths">Full paths of files to upload</param>
  388. public void AddAttachment(params string[] filePaths)
  389. {
  390. var attachments = filePaths.Select(f => new UploadAttachmentInfo(Path.GetFileName(f), _jira.FileSystem.FileReadAllBytes(f))).ToArray();
  391. AddAttachment(attachments);
  392. }
  393. /// <summary>
  394. /// Add an attachment to this issue
  395. /// </summary>
  396. /// <param name="name">Attachment name with extension</param>
  397. /// <param name="data">Attachment data</param>
  398. public void AddAttachment(string name, byte[] data)
  399. {
  400. AddAttachment(new UploadAttachmentInfo(name, data));
  401. }
  402. /// <summary>
  403. /// Add one or more attachments to this issue
  404. /// </summary>
  405. public void AddAttachment(params UploadAttachmentInfo[] attachments)
  406. {
  407. if (String.IsNullOrEmpty(_originalIssue.key))
  408. {
  409. throw new InvalidOperationException("Unable to upload attachments to server, issue has not been created.");
  410. }
  411. var content = new List<string>();
  412. var names = new List<string>();
  413. foreach (var a in attachments)
  414. {
  415. names.Add(a.Name);
  416. content.Add(Convert.ToBase64String(a.Data));
  417. }
  418. _jira.WithToken(token =>
  419. {
  420. _jira.RemoteService.AddBase64EncodedAttachmentsToIssue(
  421. token,
  422. _originalIssue.key,
  423. names.ToArray(),
  424. content.ToArray());
  425. });
  426. }
  427. /// <summary>
  428. /// Retrieve comments from server for this issue
  429. /// </summary>
  430. public ReadOnlyCollection<Comment> GetComments()
  431. {
  432. if (String.IsNullOrEmpty(_originalIssue.key))
  433. {
  434. throw new InvalidOperationException("Unable to retrieve comments from server, issue has not been created.");
  435. }
  436. return _jira.WithToken(token =>
  437. {
  438. return _jira.RemoteService.GetCommentsFromIssue(token, _originalIssue.key).Select(c => new Comment(c)).ToList().AsReadOnly();
  439. });
  440. }
  441. /// <summary>
  442. /// Add a comment to this issue
  443. /// </summary>
  444. /// <param name="comment">Comment text to add</param>
  445. public void AddComment(string comment)
  446. {
  447. if (String.IsNullOrEmpty(_originalIssue.key))
  448. {
  449. throw new InvalidOperationException("Unable to add comment to issue, issue has not been created.");
  450. }
  451. var newComment = new Comment() { Author = _jira.UserName, Body = comment };
  452. _jira.WithToken(token =>
  453. {
  454. _jira.RemoteService.AddComment(token, _originalIssue.key, newComment.toRemote());
  455. });
  456. }
  457. /// <summary>
  458. /// Add labels to this issue
  459. /// </summary>
  460. /// <param name="labels">Label(s) to add</param>
  461. public void AddLabels(params string[] labels)
  462. {
  463. if (String.IsNullOrEmpty(_originalIssue.key))
  464. {
  465. throw new InvalidOperationException("Unable to add label to issue, issue has not been created.");
  466. }
  467. var fields = new RemoteFieldValue[] {
  468. new RemoteFieldValue() {
  469. id="labels",
  470. values = labels
  471. }
  472. };
  473. UpdateRemoteFields(fields);
  474. }
  475. /// <summary>
  476. /// Adds a worklog to this issue.
  477. /// </summary>
  478. /// <param name="timespent">Specifies a time duration in JIRA duration format, representing the time spent working on the worklog</param>
  479. /// <param name="worklogStrategy">How to handle the remaining estimate, defaults to AutoAdjustRemainingEstimate</param>
  480. /// <param name="newEstimate">New estimate (only used if worklogStrategy set to NewRemainingEstimate)</param>
  481. /// <returns>Worklog as constructed by server</returns>
  482. public Worklog AddWorklog(string timespent,
  483. WorklogStrategy worklogStrategy = WorklogStrategy.AutoAdjustRemainingEstimate,
  484. string newEstimate = null)
  485. {
  486. return AddWorklog(new Worklog(timespent, DateTime.Now), worklogStrategy, newEstimate);
  487. }
  488. /// <summary>
  489. /// Adds a worklog to this issue.
  490. /// </summary>
  491. /// <param name="worklog">The worklog instance to add</param>
  492. /// <param name="worklogStrategy">How to handle the remaining estimate, defaults to AutoAdjustRemainingEstimate</param>
  493. /// <param name="newEstimate">New estimate (only used if worklogStrategy set to NewRemainingEstimate)</param>
  494. /// <returns>Worklog as constructed by server</returns>
  495. public Worklog AddWorklog(Worklog worklog,
  496. WorklogStrategy worklogStrategy = WorklogStrategy.AutoAdjustRemainingEstimate,
  497. string newEstimate = null)
  498. {
  499. if (String.IsNullOrEmpty(_originalIssue.key))
  500. {
  501. throw new InvalidOperationException("Unable to add worklog to issue, issue has not been saved to server.");
  502. }
  503. RemoteWorklog remoteWorklog = worklog.ToRemote();
  504. _jira.WithToken(token =>
  505. {
  506. switch (worklogStrategy)
  507. {
  508. case WorklogStrategy.RetainRemainingEstimate:
  509. remoteWorklog = _jira.RemoteService.AddWorklogAndRetainRemainingEstimate(token, _originalIssue.key, remoteWorklog);
  510. break;
  511. case WorklogStrategy.NewRemainingEstimate:
  512. remoteWorklog = _jira.RemoteService.AddWorklogWithNewRemainingEstimate(token, _originalIssue.key, remoteWorklog, newEstimate);
  513. break;
  514. default:
  515. remoteWorklog = _jira.RemoteService.AddWorklogAndAutoAdjustRemainingEstimate(token, _originalIssue.key, remoteWorklog);
  516. break;
  517. }
  518. });
  519. return new Worklog(remoteWorklog);
  520. }
  521. /// <summary>
  522. /// Deletes the worklog with the given id and updates the remaining estimate field on the isssue
  523. /// </summary>
  524. public void DeleteWorklog(Worklog worklog, WorklogStrategy worklogStrategy = WorklogStrategy.AutoAdjustRemainingEstimate, string newEstimate = null)
  525. {
  526. if (String.IsNullOrEmpty(_originalIssue.key))
  527. {
  528. throw new InvalidOperationException("Unable to delete worklog from issue, issue has not been saved to server.");
  529. }
  530. Jira.WithToken((token, client) =>
  531. {
  532. switch (worklogStrategy)
  533. {
  534. case WorklogStrategy.AutoAdjustRemainingEstimate:
  535. client.DeleteWorklogAndAutoAdjustRemainingEstimate(token, worklog.Id);
  536. break;
  537. case WorklogStrategy.RetainRemainingEstimate:
  538. client.DeleteWorklogAndRetainRemainingEstimate(token, worklog.Id);
  539. break;
  540. case WorklogStrategy.NewRemainingEstimate:
  541. client.DeleteWorklogWithNewRemainingEstimate(token, worklog.Id, newEstimate);
  542. break;
  543. }
  544. });
  545. }
  546. /// <summary>
  547. /// Retrieve worklogs for current issue
  548. /// </summary>
  549. public ReadOnlyCollection<Worklog> GetWorklogs()
  550. {
  551. if (String.IsNullOrEmpty(_originalIssue.key))
  552. {
  553. throw new InvalidOperationException("Unable to retrieve worklog, issue has not been saved to server.");
  554. }
  555. return _jira.WithToken(token =>
  556. {
  557. return _jira.RemoteService.GetWorkLogs(token, _originalIssue.key).Select(w => new Worklog(w)).ToList().AsReadOnly();
  558. });
  559. }
  560. /// <summary>
  561. /// Updates all fields from server
  562. /// </summary>
  563. public void Refresh()
  564. {
  565. if (String.IsNullOrEmpty(_originalIssue.key))
  566. {
  567. throw new InvalidOperationException("Unable to refresh, issue has not been saved to server.");
  568. }
  569. var remoteIssue = _jira.WithToken(token =>
  570. {
  571. return _jira.RemoteService.GetIssuesFromJqlSearch(token, "key = " + _originalIssue.key, 1).First();
  572. });
  573. Initialize(remoteIssue);
  574. }
  575. internal RemoteIssue ToRemote()
  576. {
  577. var remote = new RemoteIssue()
  578. {
  579. assignee = this.Assignee,
  580. description = this.Description,
  581. environment = this.Environment,
  582. project = this.Project,
  583. reporter = this.Reporter,
  584. summary = this.Summary,
  585. votes = this.Votes,
  586. duedate = this.DueDate
  587. };
  588. remote.key = this.Key != null ? this.Key.Value : null;
  589. if (Status != null)
  590. {
  591. remote.status = Status.Id ?? Status.Load(_jira, Project).Id;
  592. }
  593. if (Resolution != null)
  594. {
  595. remote.resolution = Resolution.Id ?? Resolution.Load(_jira, Project).Id;
  596. }
  597. if (Priority != null)
  598. {
  599. remote.priority = Priority.Id ?? Priority.Load(_jira, Project).Id;
  600. }
  601. if (Type != null)
  602. {
  603. remote.type = Type.Id ?? Type.Load(_jira, Project).Id;
  604. }
  605. if (this.AffectsVersions.Count > 0)
  606. {
  607. remote.affectsVersions = this.AffectsVersions.Select(v => v.RemoteVersion).ToArray();
  608. }
  609. if(this.FixVersions.Count > 0)
  610. {
  611. remote.fixVersions = this.FixVersions.Select(v => v.RemoteVersion).ToArray();
  612. }
  613. if (this.Components.Count > 0)
  614. {
  615. remote.components = this.Components.Select(c => c.RemoteComponent).ToArray();
  616. }
  617. if (this.CustomFields.Count > 0)
  618. {
  619. remote.customFieldValues = this.CustomFields.Select(f => new RemoteCustomFieldValue()
  620. {
  621. customfieldId = f.Id,
  622. values = f.Values
  623. }).ToArray();
  624. }
  625. return remote;
  626. }
  627. /// <summary>
  628. /// Gets the workflow actions that the issue can be transitioned to
  629. /// </summary>
  630. /// <returns></returns>
  631. public IEnumerable<JiraNamedEntity> GetAvailableActions()
  632. {
  633. if (String.IsNullOrEmpty(_originalIssue.key))
  634. {
  635. throw new InvalidOperationException("Unable to retrieve actions, issue has not been saved to server.");
  636. }
  637. return _jira.WithToken(token =>
  638. {
  639. return _jira.RemoteService.GetAvailableActions(token, _originalIssue.key).Select(a => new JiraNamedEntity(a));
  640. });
  641. }
  642. /// <summary>
  643. /// Gets the RemoteFields representing the fields that were updated
  644. /// </summary>
  645. RemoteFieldValue[] IRemoteIssueFieldProvider.GetRemoteFields()
  646. {
  647. var fields = new List<RemoteFieldValue>();
  648. var remoteFields = typeof(RemoteIssue).GetProperties();
  649. foreach (var localProperty in typeof(Issue).GetProperties())
  650. {
  651. if (typeof(IRemoteIssueFieldProvider).IsAssignableFrom(localProperty.PropertyType))
  652. {
  653. var fieldsProvider = localProperty.GetValue(this, null) as IRemoteIssueFieldProvider;
  654. if (fieldsProvider != null)
  655. {
  656. fields.AddRange(fieldsProvider.GetRemoteFields());
  657. }
  658. }
  659. else
  660. {
  661. var remoteProperty = remoteFields.FirstOrDefault(i => i.Name.Equals(localProperty.Name, StringComparison.OrdinalIgnoreCase));
  662. if (remoteProperty == null)
  663. {
  664. continue;
  665. }
  666. var localStringValue = GetStringValueForProperty(this, localProperty);
  667. var remoteStringValue = GetStringValueForProperty(_originalIssue, remoteProperty);
  668. if (remoteStringValue != localStringValue)
  669. {
  670. var remoteFieldName = remoteProperty.Name;
  671. var remoteFieldNameAttr = localProperty.GetCustomAttributes(typeof(RemoteFieldNameAttribute), true).OfType<RemoteFieldNameAttribute>().FirstOrDefault();
  672. if (remoteFieldNameAttr != null)
  673. {
  674. remoteFieldName = remoteFieldNameAttr.Name;
  675. }
  676. fields.Add(new RemoteFieldValue()
  677. {
  678. id = remoteFieldName,
  679. values = new string[1] { localStringValue }
  680. });
  681. }
  682. }
  683. }
  684. return fields.ToArray();
  685. }
  686. private string GetStringValueForProperty(object container, PropertyInfo property)
  687. {
  688. var value = property.GetValue(container, null);
  689. if (property.PropertyType == typeof(DateTime?))
  690. {
  691. var dateValue = (DateTime?)value;
  692. return dateValue.HasValue ? dateValue.Value.ToString("d/MMM/yy") : null;
  693. }
  694. else if (typeof(JiraNamedEntity).IsAssignableFrom(property.PropertyType))
  695. {
  696. var jiraNamedEntity = property.GetValue(container, null) as JiraNamedEntity;
  697. if (jiraNamedEntity != null)
  698. {
  699. return jiraNamedEntity.Id ?? jiraNamedEntity.Load(_jira, this.Project).Id;
  700. }
  701. return null;
  702. }
  703. else
  704. {
  705. return value != null ? value.ToString() : null;
  706. }
  707. }
  708. }
  709. }