PageRenderTime 65ms CodeModel.GetById 2ms app.highlight 56ms RepoModel.GetById 1ms app.codeStats 1ms

/Atlassian.Jira/Remote/IssueService.cs

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