PageRenderTime 30ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/Atlassian.Jira/Remote/IssueService.cs

https://bitbucket.org/emilianionascu/atlassian.net-sdk
C# | 535 lines | 443 code | 90 blank | 2 comment | 33 complexity | 0d4e054ecf54d917d35f1c13969631c9 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using Atlassian.Jira.Linq;
  8. using Newtonsoft.Json;
  9. using Newtonsoft.Json.Linq;
  10. using RestSharp;
  11. namespace Atlassian.Jira.Remote
  12. {
  13. internal class IssueService : IIssueService
  14. {
  15. private const int DEFAULT_MAX_ISSUES_PER_REQUEST = 20;
  16. private readonly Jira _jira;
  17. private readonly JiraRestClientSettings _restSettings;
  18. private JsonSerializerSettings _serializerSettings;
  19. private class JqlOptions
  20. {
  21. public JqlOptions(string jql, CancellationToken token)
  22. {
  23. this.Jql = jql;
  24. this.Token = token;
  25. }
  26. public string Jql { get; private set; }
  27. public CancellationToken Token { get; private set; }
  28. public int MaxIssuesPerRequest { get; set; } = DEFAULT_MAX_ISSUES_PER_REQUEST;
  29. public int StartAt { get; set; } = 0;
  30. public bool ValidateQuery { get; set; } = true;
  31. }
  32. public IssueService(Jira jira, JiraRestClientSettings restSettings)
  33. {
  34. _jira = jira;
  35. _restSettings = restSettings;
  36. }
  37. public JiraQueryable<Issue> Queryable
  38. {
  39. get
  40. {
  41. var translator = _jira.Services.Get<IJqlExpressionVisitor>();
  42. var provider = new JiraQueryProvider(translator, this);
  43. return new JiraQueryable<Issue>(provider);
  44. }
  45. }
  46. public bool ValidateQuery { get; set; } = true;
  47. public int MaxIssuesPerRequest { get; set; } = DEFAULT_MAX_ISSUES_PER_REQUEST;
  48. private async Task<JsonSerializerSettings> GetIssueSerializerSettingsAsync(CancellationToken token)
  49. {
  50. if (this._serializerSettings == null)
  51. {
  52. var fieldService = _jira.Services.Get<IIssueFieldService>();
  53. var customFields = await fieldService.GetCustomFieldsAsync(token).ConfigureAwait(false);
  54. var remoteFields = customFields.Select(f => f.RemoteField);
  55. var serializers = new Dictionary<string, ICustomFieldValueSerializer>(this._restSettings.CustomFieldSerializers, StringComparer.InvariantCultureIgnoreCase);
  56. this._serializerSettings = new JsonSerializerSettings();
  57. this._serializerSettings.NullValueHandling = NullValueHandling.Ignore;
  58. this._serializerSettings.Converters.Add(new RemoteIssueJsonConverter(remoteFields, serializers));
  59. }
  60. return this._serializerSettings;
  61. }
  62. public async Task<Issue> GetIssueAsync(string issueKey, CancellationToken token = default(CancellationToken))
  63. {
  64. var resource = String.Format("rest/api/2/issue/{0}", issueKey);
  65. var response = await _jira.RestClient.ExecuteRequestAsync(Method.GET, resource, null, token).ConfigureAwait(false);
  66. var serializerSettings = await GetIssueSerializerSettingsAsync(token).ConfigureAwait(false);
  67. var issue = JsonConvert.DeserializeObject<RemoteIssueWrapper>(response.ToString(), serializerSettings);
  68. return new Issue(_jira, issue.RemoteIssue);
  69. }
  70. public Task<IPagedQueryResult<Issue>> GetIssuesFromJqlAsync(string jql, int? maxIssues = default(int?), int startAt = 0, CancellationToken token = default(CancellationToken))
  71. {
  72. return GetIssuesFromJqlAsync(new JqlOptions(jql, token)
  73. {
  74. MaxIssuesPerRequest = maxIssues ?? this.MaxIssuesPerRequest,
  75. StartAt = startAt,
  76. ValidateQuery = this.ValidateQuery
  77. });
  78. }
  79. private async Task<IPagedQueryResult<Issue>> GetIssuesFromJqlAsync(JqlOptions options)
  80. {
  81. if (_jira.Debug)
  82. {
  83. Trace.WriteLine("[GetFromJqlAsync] JQL: " + options.Jql);
  84. }
  85. var parameters = new
  86. {
  87. jql = options.Jql,
  88. startAt = options.StartAt,
  89. maxResults = options.MaxIssuesPerRequest,
  90. validateQuery = options.ValidateQuery,
  91. };
  92. var result = await _jira.RestClient.ExecuteRequestAsync(Method.POST, "rest/api/2/search", parameters, options.Token).ConfigureAwait(false);
  93. var serializerSettings = await this.GetIssueSerializerSettingsAsync(options.Token).ConfigureAwait(false);
  94. var issues = result["issues"]
  95. .Cast<JObject>()
  96. .Select(issueJson =>
  97. {
  98. var remoteIssue = JsonConvert.DeserializeObject<RemoteIssueWrapper>(issueJson.ToString(), serializerSettings).RemoteIssue;
  99. return new Issue(_jira, remoteIssue);
  100. });
  101. return PagedQueryResult<Issue>.FromJson((JObject)result, issues);
  102. }
  103. public async Task UpdateIssueAsync(Issue issue, IssueUpdateOptions options, CancellationToken token = default(CancellationToken))
  104. {
  105. var resource = String.Format("rest/api/2/issue/{0}", issue.Key.Value);
  106. if (options.SuppressEmailNotification)
  107. {
  108. resource += "?notifyUsers=false";
  109. }
  110. var fieldProvider = issue as IRemoteIssueFieldProvider;
  111. var remoteFields = await fieldProvider.GetRemoteFieldValuesAsync(token).ConfigureAwait(false);
  112. var remoteIssue = await issue.ToRemoteAsync(token).ConfigureAwait(false);
  113. var fields = await this.BuildFieldsObjectFromIssueAsync(remoteIssue, remoteFields, token).ConfigureAwait(false);
  114. await _jira.RestClient.ExecuteRequestAsync(Method.PUT, resource, new { fields = fields }, token).ConfigureAwait(false);
  115. }
  116. public Task UpdateIssueAsync(Issue issue, CancellationToken token = default(CancellationToken))
  117. {
  118. var options = new IssueUpdateOptions();
  119. return UpdateIssueAsync(issue, options, token);
  120. }
  121. public async Task<string> CreateIssueAsync(Issue issue, CancellationToken token = default(CancellationToken))
  122. {
  123. var remoteIssue = await issue.ToRemoteAsync(token).ConfigureAwait(false);
  124. var remoteIssueWrapper = new RemoteIssueWrapper(remoteIssue, issue.ParentIssueKey);
  125. var serializerSettings = await this.GetIssueSerializerSettingsAsync(token).ConfigureAwait(false);
  126. var requestBody = JsonConvert.SerializeObject(remoteIssueWrapper, serializerSettings);
  127. var result = await _jira.RestClient.ExecuteRequestAsync(Method.POST, "rest/api/2/issue", requestBody, token).ConfigureAwait(false);
  128. return (string)result["key"];
  129. }
  130. private async Task<JObject> BuildFieldsObjectFromIssueAsync(RemoteIssue remoteIssue, RemoteFieldValue[] remoteFields, CancellationToken token)
  131. {
  132. var issueWrapper = new RemoteIssueWrapper(remoteIssue);
  133. var serializerSettings = await this.GetIssueSerializerSettingsAsync(token).ConfigureAwait(false);
  134. var issueJson = JsonConvert.SerializeObject(issueWrapper, serializerSettings);
  135. var fieldsJsonSerializerSettings = new JsonSerializerSettings()
  136. {
  137. DateParseHandling = DateParseHandling.None
  138. };
  139. var issueFields = JsonConvert.DeserializeObject<JObject>(issueJson, fieldsJsonSerializerSettings)["fields"] as JObject;
  140. var updateFields = new JObject();
  141. foreach (var field in remoteFields)
  142. {
  143. var issueFieldName = field.id;
  144. var issueFieldValue = issueFields[issueFieldName];
  145. if (issueFieldValue == null && issueFieldName.Equals("components", StringComparison.OrdinalIgnoreCase))
  146. {
  147. // JIRA does not accept 'null' as a valid value for the 'components' field.
  148. // So if the components field has been cleared it must be set to empty array instead.
  149. issueFieldValue = new JArray();
  150. }
  151. updateFields.Add(issueFieldName, issueFieldValue);
  152. }
  153. return updateFields;
  154. }
  155. public async Task ExecuteWorkflowActionAsync(Issue issue, string actionName, WorkflowTransitionUpdates updates, CancellationToken token = default(CancellationToken))
  156. {
  157. var actions = await this.GetActionsAsync(issue.Key.Value, token).ConfigureAwait(false);
  158. var action = actions.FirstOrDefault(a => a.Name.Equals(actionName, StringComparison.OrdinalIgnoreCase));
  159. if (action == null)
  160. {
  161. throw new InvalidOperationException(String.Format("Workflow action with name '{0}' not found.", actionName));
  162. }
  163. updates = updates ?? new WorkflowTransitionUpdates();
  164. var resource = String.Format("rest/api/2/issue/{0}/transitions", issue.Key.Value);
  165. var fieldProvider = issue as IRemoteIssueFieldProvider;
  166. var remoteFields = await fieldProvider.GetRemoteFieldValuesAsync(token).ConfigureAwait(false);
  167. var remoteIssue = await issue.ToRemoteAsync(token).ConfigureAwait(false);
  168. var fields = await BuildFieldsObjectFromIssueAsync(remoteIssue, remoteFields, token).ConfigureAwait(false);
  169. var updatesObject = new JObject();
  170. if (!String.IsNullOrEmpty(updates.Comment))
  171. {
  172. updatesObject.Add("comment", new JArray(new JObject[]
  173. {
  174. new JObject(new JProperty("add",
  175. new JObject(new JProperty("body", updates.Comment))))
  176. }));
  177. }
  178. var requestBody = new
  179. {
  180. transition = new
  181. {
  182. id = action.Id
  183. },
  184. update = updatesObject,
  185. fields = fields
  186. };
  187. await _jira.RestClient.ExecuteRequestAsync(Method.POST, resource, requestBody, token).ConfigureAwait(false);
  188. }
  189. public async Task<IssueTimeTrackingData> GetTimeTrackingDataAsync(string issueKey, CancellationToken token = default(CancellationToken))
  190. {
  191. if (String.IsNullOrEmpty(issueKey))
  192. {
  193. throw new InvalidOperationException("Unable to retrieve time tracking data, make sure the issue has been created.");
  194. }
  195. var resource = String.Format("rest/api/2/issue/{0}?fields=timetracking", issueKey);
  196. var response = await _jira.RestClient.ExecuteRequestAsync(Method.GET, resource, null, token).ConfigureAwait(false);
  197. var serializerSettings = _jira.RestClient.Settings.JsonSerializerSettings;
  198. var timeTrackingJson = response["fields"]?["timetracking"];
  199. if (timeTrackingJson != null)
  200. {
  201. return JsonConvert.DeserializeObject<IssueTimeTrackingData>(timeTrackingJson.ToString(), serializerSettings);
  202. }
  203. else
  204. {
  205. return null;
  206. }
  207. }
  208. public async Task<IDictionary<string, IssueFieldEditMetadata>> GetFieldsEditMetadataAsync(string issueKey, CancellationToken token = default(CancellationToken))
  209. {
  210. var dict = new Dictionary<string, IssueFieldEditMetadata>();
  211. var resource = String.Format("rest/api/2/issue/{0}/editmeta", issueKey);
  212. var serializer = JsonSerializer.Create(_jira.RestClient.Settings.JsonSerializerSettings);
  213. var result = await _jira.RestClient.ExecuteRequestAsync(Method.GET, resource, null, token).ConfigureAwait(false);
  214. JObject fields = result["fields"].Value<JObject>();
  215. foreach (var prop in fields.Properties())
  216. {
  217. var fieldName = (prop.Value["name"] ?? prop.Name).ToString();
  218. dict.Add(fieldName, prop.Value.ToObject<IssueFieldEditMetadata>(serializer));
  219. }
  220. return dict;
  221. }
  222. public async Task<Comment> AddCommentAsync(string issueKey, Comment comment, CancellationToken token = default(CancellationToken))
  223. {
  224. if (String.IsNullOrEmpty(comment.Author))
  225. {
  226. throw new InvalidOperationException("Unable to add comment due to missing author field.");
  227. }
  228. var resource = String.Format("rest/api/2/issue/{0}/comment", issueKey);
  229. var remoteComment = await _jira.RestClient.ExecuteRequestAsync<RemoteComment>(Method.POST, resource, comment.toRemote(), token).ConfigureAwait(false);
  230. return new Comment(remoteComment);
  231. }
  232. public async Task<IPagedQueryResult<Comment>> GetPagedCommentsAsync(string issueKey, int? maxComments = default(int?), int startAt = 0, CancellationToken token = default(CancellationToken))
  233. {
  234. var resource = $"rest/api/2/issue/{issueKey}/comment?startAt={startAt}";
  235. if (maxComments.HasValue)
  236. {
  237. resource += $"&maxResults={maxComments.Value}";
  238. }
  239. var result = await _jira.RestClient.ExecuteRequestAsync(Method.GET, resource).ConfigureAwait(false);
  240. var serializerSettings = _jira.RestClient.Settings.JsonSerializerSettings;
  241. var comments = result["comments"]
  242. .Cast<JObject>()
  243. .Select(commentJson =>
  244. {
  245. var remoteComment = JsonConvert.DeserializeObject<RemoteComment>(commentJson.ToString(), serializerSettings);
  246. return new Comment(remoteComment);
  247. });
  248. return PagedQueryResult<Comment>.FromJson((JObject)result, comments);
  249. }
  250. public async Task<IEnumerable<JiraNamedEntity>> GetActionsAsync(string issueKey, CancellationToken token = default(CancellationToken))
  251. {
  252. var resource = String.Format("rest/api/2/issue/{0}/transitions", issueKey);
  253. var serializerSettings = _jira.RestClient.Settings.JsonSerializerSettings;
  254. var result = await _jira.RestClient.ExecuteRequestAsync(Method.GET, resource, null, token).ConfigureAwait(false);
  255. var remoteTransitions = JsonConvert.DeserializeObject<RemoteNamedObject[]>(result["transitions"].ToString(), serializerSettings);
  256. return remoteTransitions.Select(transition => new JiraNamedEntity(transition));
  257. }
  258. public async Task<IEnumerable<Attachment>> GetAttachmentsAsync(string issueKey, CancellationToken token = default(CancellationToken))
  259. {
  260. var resource = String.Format("rest/api/2/issue/{0}?fields=attachment", issueKey);
  261. var serializerSettings = _jira.RestClient.Settings.JsonSerializerSettings;
  262. var result = await _jira.RestClient.ExecuteRequestAsync(Method.GET, resource, null, token).ConfigureAwait(false);
  263. var attachmentsJson = result["fields"]["attachment"];
  264. var attachments = JsonConvert.DeserializeObject<RemoteAttachment[]>(attachmentsJson.ToString(), serializerSettings);
  265. return attachments.Select(remoteAttachment => new Attachment(_jira, new WebClientWrapper(_jira), remoteAttachment));
  266. }
  267. public async Task<string[]> GetLabelsAsync(string issueKey, CancellationToken token = default(CancellationToken))
  268. {
  269. var resource = String.Format("rest/api/2/issue/{0}?fields=labels", issueKey);
  270. var serializerSettings = await this.GetIssueSerializerSettingsAsync(token).ConfigureAwait(false);
  271. var response = await _jira.RestClient.ExecuteRequestAsync(Method.GET, resource).ConfigureAwait(false);
  272. var issue = JsonConvert.DeserializeObject<RemoteIssueWrapper>(response.ToString(), serializerSettings);
  273. return issue.RemoteIssue.labels ?? new string[0];
  274. }
  275. public Task SetLabelsAsync(string issueKey, string[] labels, CancellationToken token = default(CancellationToken))
  276. {
  277. var resource = String.Format("rest/api/2/issue/{0}", issueKey);
  278. return _jira.RestClient.ExecuteRequestAsync(Method.PUT, resource, new
  279. {
  280. fields = new
  281. {
  282. labels = labels
  283. }
  284. }, token);
  285. }
  286. public async Task<IEnumerable<JiraUser>> GetWatchersAsync(string issueKey, CancellationToken token = default(CancellationToken))
  287. {
  288. if (string.IsNullOrEmpty(issueKey))
  289. {
  290. throw new InvalidOperationException("Unable to interact with the watchers resource, make sure the issue has been created.");
  291. }
  292. var resourceUrl = String.Format("rest/api/2/issue/{0}/watchers", issueKey);
  293. var serializerSettings = _jira.RestClient.Settings.JsonSerializerSettings;
  294. var result = await _jira.RestClient.ExecuteRequestAsync(Method.GET, resourceUrl, null, token).ConfigureAwait(false);
  295. var watchersJson = result["watchers"];
  296. return watchersJson.Select(watcherJson => JsonConvert.DeserializeObject<JiraUser>(watcherJson.ToString(), serializerSettings));
  297. }
  298. public async Task<IEnumerable<IssueChangeLog>> GetChangeLogsAsync(string issueKey, CancellationToken token = default(CancellationToken))
  299. {
  300. var resourceUrl = String.Format("rest/api/2/issue/{0}?fields=created&expand=changelog", issueKey);
  301. var serializerSettings = _jira.RestClient.Settings.JsonSerializerSettings;
  302. var response = await _jira.RestClient.ExecuteRequestAsync(Method.GET, resourceUrl, null, token).ConfigureAwait(false);
  303. var result = Enumerable.Empty<IssueChangeLog>();
  304. var changeLogs = response["changelog"];
  305. if (changeLogs != null)
  306. {
  307. var histories = changeLogs["histories"];
  308. if (histories != null)
  309. {
  310. result = histories.Select(history => JsonConvert.DeserializeObject<IssueChangeLog>(history.ToString(), serializerSettings));
  311. }
  312. }
  313. return result;
  314. }
  315. public Task DeleteWatcherAsync(string issueKey, string username, CancellationToken token = default(CancellationToken))
  316. {
  317. if (string.IsNullOrEmpty(issueKey))
  318. {
  319. throw new InvalidOperationException("Unable to interact with the watchers resource, make sure the issue has been created.");
  320. }
  321. var resourceUrl = String.Format("rest/api/2/issue/{0}/watchers?username={1}", issueKey, System.Uri.EscapeDataString(username));
  322. return _jira.RestClient.ExecuteRequestAsync(Method.DELETE, resourceUrl, null, token);
  323. }
  324. public Task AddWatcherAsync(string issueKey, string username, CancellationToken token = default(CancellationToken))
  325. {
  326. if (string.IsNullOrEmpty(issueKey))
  327. {
  328. throw new InvalidOperationException("Unable to interact with the watchers resource, make sure the issue has been created.");
  329. }
  330. var requestBody = String.Format("\"{0}\"", username);
  331. var resourceUrl = String.Format("rest/api/2/issue/{0}/watchers", issueKey);
  332. return _jira.RestClient.ExecuteRequestAsync(Method.POST, resourceUrl, requestBody, token);
  333. }
  334. public Task<IPagedQueryResult<Issue>> GetSubTasksAsync(string issueKey, int? maxIssues = default(int?), int startAt = 0, CancellationToken token = default(CancellationToken))
  335. {
  336. var jql = String.Format("parent = {0}", issueKey);
  337. return GetIssuesFromJqlAsync(jql, maxIssues, startAt, token);
  338. }
  339. public Task AddAttachmentsAsync(string issueKey, UploadAttachmentInfo[] attachments, CancellationToken token = default(CancellationToken))
  340. {
  341. var resource = String.Format("rest/api/2/issue/{0}/attachments", issueKey);
  342. var request = new RestRequest();
  343. request.Method = Method.POST;
  344. request.Resource = resource;
  345. request.AddHeader("X-Atlassian-Token", "nocheck");
  346. request.AlwaysMultipartFormData = true;
  347. foreach (var attachment in attachments)
  348. {
  349. request.AddFile("file", attachment.Data, attachment.Name);
  350. }
  351. return _jira.RestClient.ExecuteRequestAsync(request, token);
  352. }
  353. public Task DeleteAttachmentAsync(string issueKey, string attachmentId, CancellationToken token = default(CancellationToken))
  354. {
  355. var resource = String.Format("rest/api/2/attachment/{0}", attachmentId);
  356. return _jira.RestClient.ExecuteRequestAsync(Method.DELETE, resource, null, token);
  357. }
  358. public async Task<IDictionary<string, Issue>> GetIssuesAsync(IEnumerable<string> issueKeys, CancellationToken token = default(CancellationToken))
  359. {
  360. if (issueKeys.Any())
  361. {
  362. var distinctKeys = issueKeys.Distinct();
  363. var jql = String.Format("key in ({0})", String.Join(",", distinctKeys));
  364. var result = await this.GetIssuesFromJqlAsync(new JqlOptions(jql, token)
  365. {
  366. MaxIssuesPerRequest = distinctKeys.Count(),
  367. ValidateQuery = false
  368. }).ConfigureAwait(false);
  369. return result.ToDictionary<Issue, string>(i => i.Key.Value);
  370. }
  371. else
  372. {
  373. return new Dictionary<string, Issue>();
  374. }
  375. }
  376. public Task<IDictionary<string, Issue>> GetIssuesAsync(params string[] issueKeys)
  377. {
  378. return this.GetIssuesAsync(issueKeys, default(CancellationToken));
  379. }
  380. public async Task<IEnumerable<Comment>> GetCommentsAsync(string issueKey, CancellationToken token = default(CancellationToken))
  381. {
  382. var resource = String.Format("rest/api/2/issue/{0}/comment?expand=properties", issueKey);
  383. var serializerSettings = _jira.RestClient.Settings.JsonSerializerSettings;
  384. var issueJson = await _jira.RestClient.ExecuteRequestAsync(Method.GET, resource, null, token).ConfigureAwait(false);
  385. var commentJson = issueJson["comments"];
  386. var remoteComments = JsonConvert.DeserializeObject<RemoteComment[]>(commentJson.ToString(), serializerSettings);
  387. return remoteComments.Select(c => new Comment(c));
  388. }
  389. public Task DeleteCommentAsync(string issueKey, string commentId, CancellationToken token = default(CancellationToken))
  390. {
  391. var resource = String.Format("rest/api/2/issue/{0}/comment/{1}", issueKey, commentId);
  392. return _jira.RestClient.ExecuteRequestAsync(Method.DELETE, resource, null, token);
  393. }
  394. public async Task<Worklog> AddWorklogAsync(string issueKey, Worklog worklog, WorklogStrategy worklogStrategy = WorklogStrategy.AutoAdjustRemainingEstimate, string newEstimate = null, CancellationToken token = default(CancellationToken))
  395. {
  396. var remoteWorklog = worklog.ToRemote();
  397. string queryString = null;
  398. if (worklogStrategy == WorklogStrategy.RetainRemainingEstimate)
  399. {
  400. queryString = "adjustEstimate=leave";
  401. }
  402. else if (worklogStrategy == WorklogStrategy.NewRemainingEstimate)
  403. {
  404. queryString = "adjustEstimate=new&newEstimate=" + Uri.EscapeDataString(newEstimate);
  405. }
  406. var resource = String.Format("rest/api/2/issue/{0}/worklog?{1}", issueKey, queryString);
  407. var serverWorklog = await _jira.RestClient.ExecuteRequestAsync<RemoteWorklog>(Method.POST, resource, remoteWorklog, token).ConfigureAwait(false);
  408. return new Worklog(serverWorklog);
  409. }
  410. public Task DeleteWorklogAsync(string issueKey, string worklogId, WorklogStrategy worklogStrategy = WorklogStrategy.AutoAdjustRemainingEstimate, string newEstimate = null, CancellationToken token = default(CancellationToken))
  411. {
  412. string queryString = null;
  413. if (worklogStrategy == WorklogStrategy.RetainRemainingEstimate)
  414. {
  415. queryString = "adjustEstimate=leave";
  416. }
  417. else if (worklogStrategy == WorklogStrategy.NewRemainingEstimate)
  418. {
  419. queryString = "adjustEstimate=new&newEstimate=" + Uri.EscapeDataString(newEstimate);
  420. }
  421. var resource = String.Format("rest/api/2/issue/{0}/worklog/{1}?{2}", issueKey, worklogId, queryString);
  422. return _jira.RestClient.ExecuteRequestAsync(Method.DELETE, resource, null, token);
  423. }
  424. public async Task<IEnumerable<Worklog>> GetWorklogsAsync(string issueKey, CancellationToken token = default(CancellationToken))
  425. {
  426. var resource = String.Format("rest/api/2/issue/{0}/worklog", issueKey);
  427. var serializerSettings = _jira.RestClient.Settings.JsonSerializerSettings;
  428. var response = await _jira.RestClient.ExecuteRequestAsync(Method.GET, resource, null, token).ConfigureAwait(false);
  429. var worklogsJson = response["worklogs"];
  430. var remoteWorklogs = JsonConvert.DeserializeObject<RemoteWorklog[]>(worklogsJson.ToString(), serializerSettings);
  431. return remoteWorklogs.Select(w => new Worklog(w));
  432. }
  433. public async Task<Worklog> GetWorklogAsync(string issueKey, string worklogId, CancellationToken token = default(CancellationToken))
  434. {
  435. var resource = String.Format("rest/api/2/issue/{0}/worklog/{1}", issueKey, worklogId);
  436. var remoteWorklog = await _jira.RestClient.ExecuteRequestAsync<RemoteWorklog>(Method.GET, resource, null, token).ConfigureAwait(false);
  437. return new Worklog(remoteWorklog);
  438. }
  439. public Task DeleteIssueAsync(string issueKey, CancellationToken token = default(CancellationToken))
  440. {
  441. var resource = String.Format("rest/api/2/issue/{0}", issueKey);
  442. return _jira.RestClient.ExecuteRequestAsync(Method.DELETE, resource, null, token);
  443. }
  444. }
  445. }