PageRenderTime 72ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 1ms

/Atlassian.Jira/Remote/IssueService.cs

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