diff --git a/TechTalk.JiraRestClient/FieldAttribute.cs b/TechTalk.JiraRestClient/FieldAttribute.cs
new file mode 100644
index 0000000..1935f1c
--- /dev/null
+++ b/TechTalk.JiraRestClient/FieldAttribute.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace TechTalk.JiraRestClient
+{
+ public class FieldAttribute : Attribute
+ {
+ public string FieldName { get; set; }
+
+ public FieldAttribute(string fieldName)
+ {
+ this.FieldName = fieldName;
+ }
+ }
+}
diff --git a/TechTalk.JiraRestClient/IJiraClient.cs b/TechTalk.JiraRestClient/IJiraClient.cs
index a023181..0dc895d 100644
--- a/TechTalk.JiraRestClient/IJiraClient.cs
+++ b/TechTalk.JiraRestClient/IJiraClient.cs
@@ -9,8 +9,11 @@ namespace TechTalk.JiraRestClient
/// Returns all issues for the given project
IEnumerable> GetIssues(String projectKey);
/// Returns all issues of the specified type for the given project
- IEnumerable> GetIssues(String projectKey, String issueType);
+ IEnumerable> GetIssues(String projectKey, String issueType);
/// Returns all issues of the given type and the given project filtered by the given JQL query
+
+ IEnumerable> GetIssuesByQuery(string[] projects, string issueType, string jqlQuery);
+
IEnumerable> GetIssuesByQuery(String projectKey, String issueType, String jqlQuery);
/// Enumerates through all issues for the given project
IEnumerable> EnumerateIssues(String projectKey);
diff --git a/TechTalk.JiraRestClient/JiraClient.cs b/TechTalk.JiraRestClient/JiraClient.cs
index b13346e..f3de56a 100644
--- a/TechTalk.JiraRestClient/JiraClient.cs
+++ b/TechTalk.JiraRestClient/JiraClient.cs
@@ -7,6 +7,8 @@
using System.Text;
using RestSharp;
using RestSharp.Deserializers;
+using RestSharp.Extensions;
+using static System.String;
namespace TechTalk.JiraRestClient
{
@@ -29,8 +31,10 @@ public JiraClient(string baseUrl, string username, string password)
private RestRequest CreateRequest(Method method, String path)
{
- var request = new RestRequest { Method = method, Resource = path, RequestFormat = DataFormat.Json };
- request.AddHeader("Authorization", "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(String.Format("{0}:{1}", username, password))));
+ var request = new RestRequest { Method = method, Resource = path };
+ var encodedLogin = Encoding.ASCII.GetBytes($"{username}:{password}");
+ var base64String = Convert.ToBase64String(encodedLogin);
+ request.AddHeader("Authorization", $"Basic {base64String}");
return request;
}
@@ -64,6 +68,46 @@ public IEnumerable> GetIssuesByQuery(string projectKey, stri
return EnumerateIssuesInternal(projectKey, issueType, jqlQuery);
}
+ public IEnumerable> GetIssuesByQuery(string[] projectes, string issueType, string jqlQuery)
+ {
+ return EnumerateIssuesInternal(projectes, issueType, jqlQuery);
+ }
+
+ private IEnumerable> EnumerateIssuesInternal(string[] projectes, string issueType, string jqlQuery)
+ {
+ {
+ var queryCount = 50;
+ var resultCount = 0;
+ while (true)
+ {
+ var projectsClause = string.Join("%2C+", projectes.Select(p => $"%22{Uri.EscapeUriString(p)}%22"));
+ var jql = $"project+in+({projectsClause})";
+ if (!IsNullOrEmpty(issueType))
+ jql += $"+AND+issueType={Uri.EscapeUriString(issueType)}";
+ if (!IsNullOrEmpty(jqlQuery))
+ jql += $"+AND+{Uri.EscapeUriString(jqlQuery)}";
+ var path = $"search?jql={jql}&startAt={resultCount}&maxResults={queryCount}";
+ var request = CreateRequest(Method.GET, path);
+
+ var response = ExecuteRequest(request);
+ AssertStatus(response, HttpStatusCode.OK);
+
+ var data = deserializer.Deserialize>(response);
+ var issues = data.issues?.ToArray() ?? Enumerable.Empty>().ToArray();
+
+ foreach (var item in issues)
+ {
+ yield return item;
+ }
+
+ resultCount += issues.Length;
+
+ if (resultCount < data.total) continue;
+ else /* all issues received */ break;
+ }
+ }
+ }
+
public IEnumerable> EnumerateIssues(String projectKey)
{
return EnumerateIssues(projectKey, null);
@@ -88,12 +132,12 @@ private IEnumerable> EnumerateIssuesInternal(String projectK
var resultCount = 0;
while (true)
{
- var jql = String.Format("project={0}", Uri.EscapeUriString(projectKey));
- if (!String.IsNullOrEmpty(issueType))
- jql += String.Format("+AND+issueType={0}", Uri.EscapeUriString(issueType));
- if (!String.IsNullOrEmpty(jqlQuery))
- jql += String.Format("+AND+{0}", Uri.EscapeUriString(jqlQuery));
- var path = String.Format("search?jql={0}&startAt={1}&maxResults={2}", jql, resultCount, queryCount);
+ var jql = Format("project={0}", Uri.EscapeUriString(projectKey));
+ if (!IsNullOrEmpty(issueType))
+ jql += Format("+AND+issueType={0}", Uri.EscapeUriString(issueType));
+ if (!IsNullOrEmpty(jqlQuery))
+ jql += Format("+AND+{0}", Uri.EscapeUriString(jqlQuery));
+ var path = Format("search?jql={0}&startAt={1}&maxResults={2}", jql, resultCount, queryCount);
var request = CreateRequest(Method.GET, path);
var response = ExecuteRequest(request);
@@ -119,7 +163,7 @@ public Issue LoadIssue(String issueRef)
{
try
{
- var path = String.Format("issue/{0}", issueRef);
+ var path = Format("issue/{0}", issueRef);
var request = CreateRequest(Method.GET, path);
var response = ExecuteRequest(request);
@@ -163,11 +207,22 @@ public Issue CreateIssue(String projectKey, String issueType, TIss
if (issueFields.timetracking != null)
issueData.Add("timetracking", new { originalEstimate = issueFields.timetracking.originalEstimate });
- var propertyList = typeof(TIssueFields).GetProperties().Where(p => p.Name.StartsWith("customfield_"));
- foreach (var property in propertyList)
+ var propertyInfos = typeof(TIssueFields).GetProperties();
+ var propertiesFromAttribute = propertyInfos
+ .Select(p => new { Property = p, FieldAttribute = p.GetAttribute() })
+ .Where(a => a.FieldAttribute != null)
+ .Select(p => new NamedProperty(p.Property, p.FieldAttribute.FieldName));
+
+ var customFields = propertyInfos
+ .Where(p => p.Name.StartsWith("customfield_"))
+ .Select(p => new NamedProperty(p, p.Name));
+
+ var propertyList = customFields.Concat(propertiesFromAttribute);
+
+ foreach (var namedProperty in propertyList)
{
- var value = property.GetValue(issueFields, null);
- if (value != null) issueData.Add(property.Name, value);
+ var value = namedProperty.Property.GetValue(issueFields, null);
+ if (value != null) issueData.Add(namedProperty.FieldName, value);
}
request.AddBody(new { fields = issueData });
@@ -189,7 +244,7 @@ public Issue UpdateIssue(Issue issue)
{
try
{
- var path = String.Format("issue/{0}", issue.JiraIdentifier);
+ var path = Format("issue/{0}", issue.JiraIdentifier);
var request = CreateRequest(Method.PUT, path);
request.AddHeader("ContentType", "application/json");
@@ -228,7 +283,7 @@ public void DeleteIssue(IssueRef issue)
{
try
{
- var path = String.Format("issue/{0}?deleteSubtasks=true", issue.id);
+ var path = Format("issue/{0}?deleteSubtasks=true", issue.id);
var request = CreateRequest(Method.DELETE, path);
var response = ExecuteRequest(request);
@@ -246,7 +301,7 @@ public IEnumerable GetTransitions(IssueRef issue)
{
try
{
- var path = String.Format("issue/{0}/transitions?expand=transitions.fields", issue.id);
+ var path = Format("issue/{0}/transitions?expand=transitions.fields", issue.id);
var request = CreateRequest(Method.GET, path);
var response = ExecuteRequest(request);
@@ -266,7 +321,7 @@ public Issue TransitionIssue(IssueRef issue, Transition transition
{
try
{
- var path = String.Format("issue/{0}/transitions", issue.id);
+ var path = Format("issue/{0}/transitions", issue.id);
var request = CreateRequest(Method.POST, path);
request.AddHeader("ContentType", "application/json");
@@ -294,7 +349,7 @@ public IEnumerable GetWatchers(IssueRef issue)
{
try
{
- var path = String.Format("issue/{0}/watchers", issue.id);
+ var path = Format("issue/{0}/watchers", issue.id);
var request = CreateRequest(Method.GET, path);
var response = ExecuteRequest(request);
@@ -314,7 +369,7 @@ public IEnumerable GetComments(IssueRef issue)
{
try
{
- var path = String.Format("issue/{0}/comment", issue.id);
+ var path = Format("issue/{0}/comment", issue.id);
var request = CreateRequest(Method.GET, path);
var response = ExecuteRequest(request);
@@ -334,7 +389,7 @@ public Comment CreateComment(IssueRef issue, String comment)
{
try
{
- var path = String.Format("issue/{0}/comment", issue.id);
+ var path = Format("issue/{0}/comment", issue.id);
var request = CreateRequest(Method.POST, path);
request.AddHeader("ContentType", "application/json");
request.AddBody(new Comment { body = comment });
@@ -355,7 +410,7 @@ public void DeleteComment(IssueRef issue, Comment comment)
{
try
{
- var path = String.Format("issue/{0}/comment/{1}", issue.id, comment.id);
+ var path = Format("issue/{0}/comment/{1}", issue.id, comment.id);
var request = CreateRequest(Method.DELETE, path);
var response = ExecuteRequest(request);
@@ -378,7 +433,7 @@ public Attachment CreateAttachment(IssueRef issue, Stream fileStream, String fil
{
try
{
- var path = String.Format("issue/{0}/attachments", issue.id);
+ var path = Format("issue/{0}/attachments", issue.id);
var request = CreateRequest(Method.POST, path);
request.AddHeader("X-Atlassian-Token", "nocheck");
request.AddHeader("ContentType", "multipart/form-data");
@@ -400,7 +455,7 @@ public void DeleteAttachment(Attachment attachment)
{
try
{
- var path = String.Format("attachment/{0}", attachment.id);
+ var path = Format("attachment/{0}", attachment.id);
var request = CreateRequest(Method.DELETE, path);
var response = ExecuteRequest(request);
@@ -470,7 +525,7 @@ public void DeleteIssueLink(IssueLink link)
{
try
{
- var path = String.Format("issueLink/{0}", link.id);
+ var path = Format("issueLink/{0}", link.id);
var request = CreateRequest(Method.DELETE, path);
var response = ExecuteRequest(request);
@@ -488,7 +543,7 @@ public IEnumerable GetRemoteLinks(IssueRef issue)
{
try
{
- var path = string.Format("issue/{0}/remotelink", issue.id);
+ var path = Format("issue/{0}/remotelink", issue.id);
var request = CreateRequest(Method.GET, path);
request.AddHeader("ContentType", "application/json");
@@ -509,7 +564,7 @@ public RemoteLink CreateRemoteLink(IssueRef issue, RemoteLink remoteLink)
{
try
{
- var path = string.Format("issue/{0}/remotelink", issue.id);
+ var path = Format("issue/{0}/remotelink", issue.id);
var request = CreateRequest(Method.POST, path);
request.AddHeader("ContentType", "application/json");
request.AddBody(new
@@ -545,7 +600,7 @@ public RemoteLink UpdateRemoteLink(IssueRef issue, RemoteLink remoteLink)
{
try
{
- var path = string.Format("issue/{0}/remotelink/{1}", issue.id, remoteLink.id);
+ var path = Format("issue/{0}/remotelink/{1}", issue.id, remoteLink.id);
var request = CreateRequest(Method.PUT, path);
request.AddHeader("ContentType", "application/json");
@@ -571,7 +626,7 @@ public void DeleteRemoteLink(IssueRef issue, RemoteLink remoteLink)
{
try
{
- var path = string.Format("issue/{0}/remotelink/{1}", issue.id, remoteLink.id);
+ var path = Format("issue/{0}/remotelink/{1}", issue.id, remoteLink.id);
var request = CreateRequest(Method.DELETE, path);
request.AddHeader("ContentType", "application/json");
diff --git a/TechTalk.JiraRestClient/NamedProperty.cs b/TechTalk.JiraRestClient/NamedProperty.cs
new file mode 100644
index 0000000..dd65cdd
--- /dev/null
+++ b/TechTalk.JiraRestClient/NamedProperty.cs
@@ -0,0 +1,17 @@
+namespace TechTalk.JiraRestClient
+{
+ using System.Reflection;
+
+ internal class NamedProperty
+ {
+ public PropertyInfo Property { get; set; }
+
+ public string FieldName { get; set; }
+
+ public NamedProperty(PropertyInfo property, string fieldName)
+ {
+ this.Property = property;
+ this.FieldName = fieldName;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TechTalk.JiraRestClient/TechTalk.JiraRestClient.csproj b/TechTalk.JiraRestClient/TechTalk.JiraRestClient.csproj
index cf1f1d6..783958c 100644
--- a/TechTalk.JiraRestClient/TechTalk.JiraRestClient.csproj
+++ b/TechTalk.JiraRestClient/TechTalk.JiraRestClient.csproj
@@ -41,7 +41,9 @@
+
+
diff --git a/TechTalk.JiraRestClient/TechTalk.JiraRestClient_NoNuget.csproj b/TechTalk.JiraRestClient/TechTalk.JiraRestClient_NoNuget.csproj
new file mode 100644
index 0000000..03d3631
--- /dev/null
+++ b/TechTalk.JiraRestClient/TechTalk.JiraRestClient_NoNuget.csproj
@@ -0,0 +1,75 @@
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {210529FA-454E-4C32-A2C8-353ECBD4DA05}
+ Library
+ Properties
+ TechTalk.JiraRestClient
+ TechTalk.JiraRestClient
+ v4.6.2
+
+
+ 512
+ ..\
+ true
+
+
+
+
+
+ AnyCPU
+ bin\Debug\
+ DEBUG;TRACE
+ false
+
+
+ AnyCPU
+ bin\Release\
+ TRACE
+ false
+
+
+
+ ..\..\packages\RestSharp.104.1\lib\net4-client\RestSharp.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+
+
\ No newline at end of file
diff --git a/TechTalk.JiraRestClient/packages.config b/TechTalk.JiraRestClient/packages.config
index a64d06c..3ef434c 100644
--- a/TechTalk.JiraRestClient/packages.config
+++ b/TechTalk.JiraRestClient/packages.config
@@ -1,4 +1,4 @@
-
-
-
+
+
+
\ No newline at end of file