From cc0c83c3a0bdcb740ddcd1934f84ca14330818a2 Mon Sep 17 00:00:00 2001 From: Erdogan Kurtur Date: Fri, 12 Jun 2015 14:05:56 +0300 Subject: [PATCH] Searching of Issues and retrieving change history * Added support for searching for issues. ref: https://confluence.jetbrains.com/display/YTD6/Get+the+List+of+Issues probably not compatible with YT3 since this method is not mentioned in documentation. * Added support for retrieving change history of a single issue. ref: https://confluence.jetbrains.com/display/YTD6/Get+Historical+Changes+of+an+Issue You can use NewValue/OldValue properties for changed field values, and Values for fields set in that change --- YouTrack.Rest/Change.cs | 19 + YouTrack.Rest/ChangedField.cs | 30 ++ YouTrack.Rest/Deserialization/Change.cs | 41 +++ .../Deserialization/ChangeCollection.cs | 15 + YouTrack.Rest/Deserialization/Field.cs | 10 +- .../Deserialization/HasFieldsBase.cs | 76 ++++ YouTrack.Rest/Deserialization/Issue.cs | 67 +--- YouTrack.Rest/Deserialization/Value_.cs | 5 +- YouTrack.Rest/IChange.cs | 13 + YouTrack.Rest/IChangedField.cs | 12 + YouTrack.Rest/IIssueActions.cs | 3 +- YouTrack.Rest/IssueActions.cs | 20 +- .../Repositories/IIssueRepository.cs | 8 +- YouTrack.Rest/Repositories/IssueRepository.cs | 41 ++- .../GetChangeHistoryOfAnIssueRequest.cs | 12 + .../Requests/Issues/SearchRequest.cs | 29 ++ .../Requests/RestRequestResourceBuilder.cs | 11 + YouTrack.Rest/YouTrack.Rest.csproj | 337 +++++++++--------- 18 files changed, 514 insertions(+), 235 deletions(-) create mode 100644 YouTrack.Rest/Change.cs create mode 100644 YouTrack.Rest/ChangedField.cs create mode 100644 YouTrack.Rest/Deserialization/Change.cs create mode 100644 YouTrack.Rest/Deserialization/ChangeCollection.cs create mode 100644 YouTrack.Rest/Deserialization/HasFieldsBase.cs create mode 100644 YouTrack.Rest/IChange.cs create mode 100644 YouTrack.Rest/IChangedField.cs create mode 100644 YouTrack.Rest/Requests/Issues/GetChangeHistoryOfAnIssueRequest.cs create mode 100644 YouTrack.Rest/Requests/Issues/SearchRequest.cs diff --git a/YouTrack.Rest/Change.cs b/YouTrack.Rest/Change.cs new file mode 100644 index 0000000..a104bf7 --- /dev/null +++ b/YouTrack.Rest/Change.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace YouTrack.Rest +{ + public class Change: IChange + { + public Change() + { + ChangedFields = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + } + + public DateTime Updated { get; internal set; } + + public string UpdaterName { get; internal set; } + + public IDictionary ChangedFields { get; private set; } + } +} \ No newline at end of file diff --git a/YouTrack.Rest/ChangedField.cs b/YouTrack.Rest/ChangedField.cs new file mode 100644 index 0000000..ffdff20 --- /dev/null +++ b/YouTrack.Rest/ChangedField.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using YouTrack.Rest.Deserialization; + +namespace YouTrack.Rest +{ + class ChangedField : IChangedField + { + public ChangedField(Field field) + { + Name = field.Name; + + if (null != field.Values) + Values = field.Values.ConvertAll(v => v.ToString()); + + if (null != field.NewValues) + NewValues = field.NewValues.ConvertAll(v => v.ToString()); + + if (null != field.OldValues) + OldValues = field.OldValues.ConvertAll(v => v.ToString()); + } + + public string Name { get; private set; } + + public IEnumerable Values { get; private set; } + + public IEnumerable OldValues { get; private set; } + + public IEnumerable NewValues { get; private set; } + } +} \ No newline at end of file diff --git a/YouTrack.Rest/Deserialization/Change.cs b/YouTrack.Rest/Deserialization/Change.cs new file mode 100644 index 0000000..d41ef15 --- /dev/null +++ b/YouTrack.Rest/Deserialization/Change.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; + +namespace YouTrack.Rest.Deserialization +{ + class Change : HasFieldsBase + { + public Change() : base("Change") + { + } + + public IChange GetChange(IConnection connection) + { + Rest.Change change = new Rest.Change(); + + MapTo(change); + + return change; + } + + public void MapTo(Rest.Change change) + { + change.UpdaterName = GetString("updaterName", ""); + change.Updated = GetDateTime("updated"); + + MapFields(change.ChangedFields); + } + + private void MapFields(IDictionary fields) + { + fields.Clear(); + + foreach (Field field in Fields) + { + if (!string.IsNullOrEmpty(field.Name)) + { + fields[field.Name] = new ChangedField(field); + } + } + } + } +} \ No newline at end of file diff --git a/YouTrack.Rest/Deserialization/ChangeCollection.cs b/YouTrack.Rest/Deserialization/ChangeCollection.cs new file mode 100644 index 0000000..1d85a5f --- /dev/null +++ b/YouTrack.Rest/Deserialization/ChangeCollection.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Linq; + +namespace YouTrack.Rest.Deserialization +{ + class ChangeCollection + { + public List Changes { get; set; } + + public IEnumerable GetChanges(IConnection connection) + { + return Changes.Select(c => c.GetChange(connection)).ToArray(); + } + } +} \ No newline at end of file diff --git a/YouTrack.Rest/Deserialization/Field.cs b/YouTrack.Rest/Deserialization/Field.cs index c5d2e16..88c4d96 100644 --- a/YouTrack.Rest/Deserialization/Field.cs +++ b/YouTrack.Rest/Deserialization/Field.cs @@ -1,14 +1,20 @@ using System; using System.Collections.Generic; using System.Linq; +using RestSharp.Deserializers; using YouTrack.Rest.Exceptions; namespace YouTrack.Rest.Deserialization { class Field { - public string Name { get; set; } - public List Values { get; set; } + public string Name { get; set; } + public string Type { get; set; } + public List Values { get; set; } + + // issue field value changes + public List NewValues { get; set; } + public List OldValues { get; set; } public string GetValue() { diff --git a/YouTrack.Rest/Deserialization/HasFieldsBase.cs b/YouTrack.Rest/Deserialization/HasFieldsBase.cs new file mode 100644 index 0000000..9c69058 --- /dev/null +++ b/YouTrack.Rest/Deserialization/HasFieldsBase.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using YouTrack.Rest.Exceptions; + +namespace YouTrack.Rest.Deserialization +{ + abstract class HasFieldsBase + { + private readonly string type; + + public string Id { get; set; } + + public List Fields { get; set; } + + protected HasFieldsBase(string type) + { + this.type = type; + } + + protected bool HasFieldFor(string name) + { + return Fields.Any(GetCompareNamesPredicate(name)); + } + + protected bool HasSingleFieldFor(string name) + { + return Fields.Count(GetCompareNamesPredicate(name)) == 1; + } + + protected Field GetSingleFieldFor(string name) + { + return Fields.Single(GetCompareNamesPredicate(name)); + } + + private Func GetCompareNamesPredicate(string name) + { + return f => f.Name.ToUpper() == name.ToUpper(); + } + + protected int GetInt32(string name) + { + if (HasSingleFieldFor(name)) + { + return GetSingleFieldFor(name).GetInt32(); + } + + throw new IssueDeserializationException(String.Format("{0} '{1}' has zero or multiple integer values for field '{2}'.", type, Id, name)); + } + + protected DateTime GetDateTime(string name) + { + if (HasSingleFieldFor(name)) + { + return GetSingleFieldFor(name).GetDateTime(); + } + + throw new IssueDeserializationException(String.Format("{0} '{1}' has zero or multiple datetime values for field '{2}'.", type, Id, name)); + } + + protected string GetString(string name, string defaultValue = null) + { + if (!HasFieldFor(name) && defaultValue != null) + { + return defaultValue; + } + + if (HasSingleFieldFor(name)) + { + return GetSingleFieldFor(name).GetValue(); + } + + throw new IssueDeserializationException(String.Format("{0} '{1}' has zero or multiple string values for field '{2}'.", type, Id, name)); + } + } +} \ No newline at end of file diff --git a/YouTrack.Rest/Deserialization/Issue.cs b/YouTrack.Rest/Deserialization/Issue.cs index c4efae4..0a2e9d0 100644 --- a/YouTrack.Rest/Deserialization/Issue.cs +++ b/YouTrack.Rest/Deserialization/Issue.cs @@ -1,16 +1,16 @@ -using System; using System.Collections.Generic; using System.Linq; -using YouTrack.Rest.Exceptions; namespace YouTrack.Rest.Deserialization { //Has to have name Issue for RestSharp deserialization to work properly. - class Issue + class Issue : HasFieldsBase { - public string Id { get; set; } - public List Fields { get; set; } - public List Comments { get; set; } + public List Comments { get; set; } + + public Issue() : base("Issue") + { + } public virtual IIssue GetIssue(IConnection connection) { @@ -21,61 +21,6 @@ public virtual IIssue GetIssue(IConnection connection) return issue; } - private int GetInt32(string name) - { - if (HasSingleFieldFor(name)) - { - return GetSingleFieldFor(name).GetInt32(); - } - - throw new IssueDeserializationException(String.Format("Issue '{0}' has zero or multiple integer values for field '{1}'.", Id, name)); - } - - private DateTime GetDateTime(string name) - { - if(HasSingleFieldFor(name)) - { - return GetSingleFieldFor(name).GetDateTime(); - } - - throw new IssueDeserializationException(String.Format("Issue '{0}' has zero or multiple datetime values for field '{1}'.", Id, name)); - } - - private string GetString(string name, string defaultValue = null) - { - if(!HasFieldFor(name) && defaultValue != null) - { - return defaultValue; - } - - if (HasSingleFieldFor(name)) - { - return GetSingleFieldFor(name).GetValue(); - } - - throw new IssueDeserializationException(String.Format("Issue '{0}' has zero or multiple string values for field '{1}'.", Id, name)); - } - - private bool HasFieldFor(string name) - { - return Fields.Any(GetCompareNamesPredicate(name)); - } - - private bool HasSingleFieldFor(string name) - { - return Fields.Count(GetCompareNamesPredicate(name)) == 1; - } - - private Field GetSingleFieldFor(string name) - { - return Fields.Single(GetCompareNamesPredicate(name)); - } - - private Func GetCompareNamesPredicate(string name) - { - return f => f.Name.ToUpper() == name.ToUpper(); - } - public void MapTo(Rest.Issue issue, IConnection connection) { issue.CommentsCount = GetInt32("commentsCount"); diff --git a/YouTrack.Rest/Deserialization/Value_.cs b/YouTrack.Rest/Deserialization/Value_.cs index 4771274..f7b4dd1 100644 --- a/YouTrack.Rest/Deserialization/Value_.cs +++ b/YouTrack.Rest/Deserialization/Value_.cs @@ -1,5 +1,8 @@ namespace YouTrack.Rest.Deserialization -{ +{ + internal class NewValue : Value_ { } + internal class OldValue : Value_ { } + internal class Value_ { public string Value { get; set; } diff --git a/YouTrack.Rest/IChange.cs b/YouTrack.Rest/IChange.cs new file mode 100644 index 0000000..4343091 --- /dev/null +++ b/YouTrack.Rest/IChange.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace YouTrack.Rest +{ + public interface IChange + { + DateTime Updated { get; } + string UpdaterName { get; } + + IDictionary ChangedFields { get; } + } +} \ No newline at end of file diff --git a/YouTrack.Rest/IChangedField.cs b/YouTrack.Rest/IChangedField.cs new file mode 100644 index 0000000..d094a74 --- /dev/null +++ b/YouTrack.Rest/IChangedField.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace YouTrack.Rest +{ + public interface IChangedField + { + string Name { get; } + IEnumerable Values { get; } + IEnumerable OldValues { get; } + IEnumerable NewValues { get; } + } +} \ No newline at end of file diff --git a/YouTrack.Rest/IIssueActions.cs b/YouTrack.Rest/IIssueActions.cs index 8f89d29..5f7739f 100644 --- a/YouTrack.Rest/IIssueActions.cs +++ b/YouTrack.Rest/IIssueActions.cs @@ -14,6 +14,7 @@ public interface IIssueActions void SetSubsystem(string subsystem, string group = null); void SetType(string type, string group = null); void ApplyCommand(string command, string group = null); - void ApplyCommands(params string[] commands); + void ApplyCommands(params string[] commands); + IEnumerable ChangeHistory { get; } } } \ No newline at end of file diff --git a/YouTrack.Rest/IssueActions.cs b/YouTrack.Rest/IssueActions.cs index e39f397..88e6d58 100644 --- a/YouTrack.Rest/IssueActions.cs +++ b/YouTrack.Rest/IssueActions.cs @@ -45,8 +45,24 @@ public virtual void ApplyCommands(params string[] commands) ApplyCommandsToAnIssueRequest request = new ApplyCommandsToAnIssueRequest(Id, commands); Connection.Post(request); - } - + } + + private IEnumerable changes; + + public IEnumerable ChangeHistory + { + get { return changes ?? (changes = GetChanges()); } + } + + private IEnumerable GetChanges() + { + GetChangeHistoryOfAnIssueRequest request = new GetChangeHistoryOfAnIssueRequest(Id); + + ChangeCollection changeCollection = Connection.Get(request); + + return changeCollection.GetChanges(Connection); + } + public void SetSubsystem(string subsystem, string group = null) { ApplyCommand(Commands.SetSubsystem(subsystem), group); diff --git a/YouTrack.Rest/Repositories/IIssueRepository.cs b/YouTrack.Rest/Repositories/IIssueRepository.cs index 8e6f55a..2d3af67 100644 --- a/YouTrack.Rest/Repositories/IIssueRepository.cs +++ b/YouTrack.Rest/Repositories/IIssueRepository.cs @@ -1,9 +1,15 @@ +using System.Collections.Generic; + namespace YouTrack.Rest.Repositories { public interface IIssueRepository { IIssue CreateIssue(string project, string summary, string description, string group = null); - IIssue GetIssue(string issueId); + IIssue GetIssue(string issueId); + IEnumerable Search(string query); + IEnumerable Search(string query, params string[] withFields); + IEnumerable Search(string query, int maximumNumberOfRecordsToReturn, int startFrom); + IEnumerable Search(string query, string[] withFields, int maximumNumberOfRecordsToReturn, int startFrom); void DeleteIssue(string issueId); bool IssueExists(string issueId); } diff --git a/YouTrack.Rest/Repositories/IssueRepository.cs b/YouTrack.Rest/Repositories/IssueRepository.cs index e8fa8d0..5cb253c 100644 --- a/YouTrack.Rest/Repositories/IssueRepository.cs +++ b/YouTrack.Rest/Repositories/IssueRepository.cs @@ -1,7 +1,7 @@ +using System.Collections.Generic; using System.Linq; using YouTrack.Rest.Exceptions; using YouTrack.Rest.Factories; -using YouTrack.Rest.Requests; using YouTrack.Rest.Requests.Issues; namespace YouTrack.Rest.Repositories @@ -30,8 +30,43 @@ public IIssue CreateIssue(string project, string summary, string description, st public IIssue GetIssue(string issueId) { return issueFactory.CreateIssue(issueId, connection); - } - + } + + public IEnumerable Search(string query) + { + SearchRequest searchRequest = new SearchRequest(query); + + return Search(searchRequest); + } + + public IEnumerable Search(string query, params string[] withFields) + { + SearchRequest searchRequest = new SearchRequest(query, withFields); + + return Search(searchRequest); + } + + public IEnumerable Search(string query, int maximumNumberOfRecordsToReturn, int startFrom) + { + SearchRequest searchRequest = new SearchRequest(query, null, maximumNumberOfRecordsToReturn, startFrom); + + return Search(searchRequest); + } + + public IEnumerable Search(string query, string[] withFields, int maximumNumberOfRecordsToReturn, int startFrom) + { + SearchRequest searchRequest = new SearchRequest(query, withFields, maximumNumberOfRecordsToReturn, startFrom); + + return Search(searchRequest); + } + + private IEnumerable Search(SearchRequest request) + { + List issues = connection.Get>(request); + + return issues.Select(i => i.GetIssue(connection)); + } + public void DeleteIssue(string issueId) { DeleteIssueRequest deleteIssueRequest = new DeleteIssueRequest(issueId); diff --git a/YouTrack.Rest/Requests/Issues/GetChangeHistoryOfAnIssueRequest.cs b/YouTrack.Rest/Requests/Issues/GetChangeHistoryOfAnIssueRequest.cs new file mode 100644 index 0000000..b67cd00 --- /dev/null +++ b/YouTrack.Rest/Requests/Issues/GetChangeHistoryOfAnIssueRequest.cs @@ -0,0 +1,12 @@ +using System; + +namespace YouTrack.Rest.Requests.Issues +{ + class GetChangeHistoryOfAnIssueRequest : YouTrackRequest, IYouTrackGetRequest + { + public GetChangeHistoryOfAnIssueRequest(string issueId) + : base(String.Format("/rest/issue/{0}/changes", issueId)) + { + } + } +} \ No newline at end of file diff --git a/YouTrack.Rest/Requests/Issues/SearchRequest.cs b/YouTrack.Rest/Requests/Issues/SearchRequest.cs new file mode 100644 index 0000000..e43c24c --- /dev/null +++ b/YouTrack.Rest/Requests/Issues/SearchRequest.cs @@ -0,0 +1,29 @@ +using System.Linq; + +namespace YouTrack.Rest.Requests.Issues +{ + class SearchRequest : YouTrackRequest, IYouTrackGetRequest + { + public SearchRequest(string query, string[] withFields = null, int max = -1, int from = -1) + : base("/rest/issue") + { + if (!string.IsNullOrWhiteSpace(query)) + ResourceBuilder.AddParameter("filter", query); + + if (null != withFields) + { + withFields = withFields.Where(f => !string.IsNullOrWhiteSpace(f)) + .Select(f => f.Trim()) + .ToArray(); + + if (withFields.Length > 0) + ResourceBuilder.AddMultiValueParameter("with", withFields); + } + + if (max > 0) + ResourceBuilder.AddParameter("max", max.ToString()); + if (from >= 0) + ResourceBuilder.AddParameter("from", from.ToString()); + } + } +} \ No newline at end of file diff --git a/YouTrack.Rest/Requests/RestRequestResourceBuilder.cs b/YouTrack.Rest/Requests/RestRequestResourceBuilder.cs index c985db7..8bed61a 100644 --- a/YouTrack.Rest/Requests/RestRequestResourceBuilder.cs +++ b/YouTrack.Rest/Requests/RestRequestResourceBuilder.cs @@ -47,6 +47,17 @@ public void AddParameter(string parameterName, string parameterValue) { parameters.Add(parameterName, parameterValue); } + } + + public void AddMultiValueParameter(string parameterName, IEnumerable parameterValues) + { + foreach (var parameterValue in parameterValues) + { + if (!String.IsNullOrEmpty(parameterValue)) + { + parameters.Add(parameterName, parameterValue); + } + } } private void ThrowIfParameterAlreadyAdded(string parameterName) diff --git a/YouTrack.Rest/YouTrack.Rest.csproj b/YouTrack.Rest/YouTrack.Rest.csproj index 4e4adc4..16fcc06 100644 --- a/YouTrack.Rest/YouTrack.Rest.csproj +++ b/YouTrack.Rest/YouTrack.Rest.csproj @@ -1,171 +1,180 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {DF17421E-5AF6-4855-9CB7-73D075CCC9F7} - Library - Properties - YouTrack.Rest - YouTrack.Rest - v4.0 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - False - ..\packages\Castle.Core.3.3.3\lib\net40-client\Castle.Core.dll - - - False - ..\packages\RestSharp.105.0.1\lib\net4\RestSharp.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {DF17421E-5AF6-4855-9CB7-73D075CCC9F7} + Library + Properties + YouTrack.Rest + YouTrack.Rest + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\packages\Castle.Core.3.3.3\lib\net40-client\Castle.Core.dll + + + False + ..\packages\RestSharp.105.0.1\lib\net4\RestSharp.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + --> \ No newline at end of file