Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions TechTalk.JiraRestClient/AvatarUrls.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Collections.Generic;
using RestSharp.Deserializers;

namespace TechTalk.JiraRestClient
{
public class AvatarUrls
{
[DeserializeAs(Name = "16x16")]
public string sixteen { get; set; }
[DeserializeAs(Name = "24x24")]
public string twentyfour { get; set; }
[DeserializeAs(Name = "32x32")]
public string thirtytwo { get; set; }
[DeserializeAs(Name = "48x48")]
public string fourteigth { get; set; }
}
}
17 changes: 17 additions & 0 deletions TechTalk.JiraRestClient/IJiraClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ namespace TechTalk.JiraRestClient
{
public interface IJiraClient<TIssueFields> where TIssueFields : IssueFields, new()
{
/// <summary>Returns current logged in user</summary>
JiraUser GetLoggedInUser();

/// <summary>Returns all issues for the given project</summary>
IEnumerable<Issue<TIssueFields>> GetIssues(String projectKey);
/// <summary>Returns all issues of the specified type for the given project</summary>
IEnumerable<Issue<TIssueFields>> GetIssues(String projectKey, String issueType);
/// <summary>Returns all issues of the given type and the given project filtered by the given JQL query</summary>
IEnumerable<Issue<TIssueFields>> GetIssuesByQuery(String projectKey, String issueType, String jqlQuery);
/// <summary>Returns all issues filtered by only the given JQL query</summary>
IEnumerable<Issue<TIssueFields>> GetIssuesByQuery(String jqlQuery);
/// <summary>Enumerates through all issues for the given project</summary>
IEnumerable<Issue<TIssueFields>> EnumerateIssues(String projectKey);
/// <summary>Enumerates through all issues of the specified type for the given project</summary>
Expand Down Expand Up @@ -45,6 +50,16 @@ namespace TechTalk.JiraRestClient
/// <summary>Deletes the given comment</summary>
void DeleteComment(IssueRef issue, Comment comment);

/// <summary>Returns all worklogs for the given issue</summary>
IEnumerable<Worklog> GetWorklogs(IssueRef issue);

/// <summary>Adds a worklog to the given issue</summary>
Worklog CreateWorklog(IssueRef issue, int timespentSeconds, string comment, DateTime started);
/// <summary>Update a worklog to the given issue</summary>
Worklog UpdateWorklog(IssueRef issue, Worklog worklog);
/// <summary>Delete a worklog to the given issue</summary>
void DeleteWorklog(IssueRef issue, Worklog worklog);

/// <summary>Return all attachments for the given issue</summary>
IEnumerable<Attachment> GetAttachments(IssueRef issue);
/// <summary>Creates an attachment to the given issue</summary>
Expand Down Expand Up @@ -73,6 +88,8 @@ namespace TechTalk.JiraRestClient
/// <summary>Returns all issue types</summary>
IEnumerable<IssueType> GetIssueTypes();

JiraUser GetUser(string username);

/// <summary>Returns information about the JIRA server</summary>
ServerInfo GetServerInfo();
}
Expand Down
4 changes: 3 additions & 1 deletion TechTalk.JiraRestClient/IssueFields.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public IssueFields()

labels = new List<String>();
comments = new List<Comment>();
worklogs = new List<Worklog>();
issuelinks = new List<IssueLink>();
attachment = new List<Attachment>();
watchers = new List<JiraUser>();
Expand All @@ -24,10 +25,11 @@ public IssueFields()

public JiraUser reporter { get; set; }
public JiraUser assignee { get; set; }
public List<JiraUser> watchers { get; set; }
public List<JiraUser> watchers { get; set; }

public List<String> labels { get; set; }
public List<Comment> comments { get; set; }
public List<Worklog> worklogs { get; set; }
public List<IssueLink> issuelinks { get; set; }
public List<Attachment> attachment { get; set; }
}
Expand Down
179 changes: 169 additions & 10 deletions TechTalk.JiraRestClient/JiraClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,27 @@ namespace TechTalk.JiraRestClient

public class JiraClient<TIssueFields> : IJiraClient<TIssueFields> where TIssueFields : IssueFields, new()
{
private readonly string username;
private readonly string password;
private readonly string auth;
private readonly JsonDeserializer deserializer;
private readonly string baseApiUrl;
public JiraClient(string baseUrl, string username, string password)
{
this.username = username;
this.password = password;

baseApiUrl = new Uri(new Uri(baseUrl), "rest/api/2/").ToString();
auth = Convert.ToBase64String(Encoding.UTF8.GetBytes(String.Format("{0}:{1}", username, password)));
deserializer = new JsonDeserializer();
}

public JiraClient(string baseUrl, string authstring)
{
auth = authstring;
baseApiUrl = new Uri(new Uri(baseUrl), "rest/api/2/").ToString();
deserializer = new JsonDeserializer();
}

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, RequestFormat = DataFormat.Json, DateFormat = "yyyy-MM-ddTHH:mm:ss.fffzz00" };
request.AddHeader("Authorization", String.Format("Basic {0}", auth));
return request;
}

Expand All @@ -48,6 +52,26 @@ private void AssertStatus(IRestResponse response, HttpStatusCode status)
throw new JiraClientException("JIRA returned wrong status: " + response.StatusDescription, response.Content);
}

public JiraUser GetLoggedInUser()
{
try
{
var path = "myself";
var request = CreateRequest(Method.GET, path);

var response = ExecuteRequest(request);
AssertStatus(response, HttpStatusCode.OK);

var jiraUser = deserializer.Deserialize<JiraUser>(response);
return jiraUser;
}
catch (Exception ex)
{
Trace.TraceError("GetLoggedInUser() error: {0}", ex);
throw new JiraClientException("Could not load user", ex);
}
}


public IEnumerable<Issue<TIssueFields>> GetIssues(String projectKey)
{
Expand All @@ -64,6 +88,11 @@ public IEnumerable<Issue<TIssueFields>> GetIssuesByQuery(string projectKey, stri
return EnumerateIssuesInternal(projectKey, issueType, jqlQuery);
}

public IEnumerable<Issue<TIssueFields>> GetIssuesByQuery(string jqlQuery)
{
return EnumerateIssuesInternal("", "", jqlQuery);
}

public IEnumerable<Issue<TIssueFields>> EnumerateIssues(String projectKey)
{
return EnumerateIssues(projectKey, null);
Expand All @@ -88,11 +117,29 @@ private IEnumerable<Issue<TIssueFields>> EnumerateIssuesInternal(String projectK
var resultCount = 0;
while (true)
{
var jql = String.Format("project={0}", Uri.EscapeUriString(projectKey));
var jql = String.Empty;
if(!String.IsNullOrEmpty(projectKey))
jql += String.Format("project={0}", Uri.EscapeUriString(projectKey));
if (!String.IsNullOrEmpty(issueType))
jql += String.Format("+AND+issueType={0}", Uri.EscapeUriString(issueType));
{
if (!String.IsNullOrEmpty(jql))
{
jql += String.Format("+AND+issueType={0}", Uri.EscapeUriString(issueType));
}
else
{
jql += String.Format("issueType={0}", Uri.EscapeUriString(issueType));
}
}

if (!String.IsNullOrEmpty(jqlQuery))
jql += String.Format("+AND+{0}", Uri.EscapeUriString(jqlQuery));
{
if(!String.IsNullOrEmpty(jql))
jql += String.Format("+AND+{0}", Uri.EscapeUriString(jqlQuery));
else
jql += String.Format("{0}", Uri.EscapeUriString(jqlQuery));
}

var path = String.Format("search?jql={0}&startAt={1}&maxResults={2}", jql, resultCount, queryCount);
var request = CreateRequest(Method.GET, path);

Expand Down Expand Up @@ -368,6 +415,96 @@ public void DeleteComment(IssueRef issue, Comment comment)
}
}

public IEnumerable<Worklog> GetWorklogs(IssueRef issue)
{
try
{
var path = String.Format("issue/{0}/worklog", issue.id);
var request = CreateRequest(Method.GET, path);

var response = ExecuteRequest(request);
AssertStatus(response, HttpStatusCode.OK);

var data = deserializer.Deserialize<WorklogsContainer>(response);
return data.Worklogs ?? Enumerable.Empty<Worklog>();
}
catch (Exception ex)
{
Trace.TraceError("GetWorklogs(issue) error: {0}", ex);
throw new JiraClientException("Could not load worklogs", ex);
}
}

public Worklog CreateWorklog(IssueRef issue, int timespentSeconds, string comment, DateTime started)
{
try
{
var path = String.Format("issue/{0}/worklog", issue.id);
var request = CreateRequest(Method.POST, path);
request.AddHeader("ContentType", "application/json");

var insert = new Dictionary<string, object>();
insert.Add("started", started.ToString("yyyy-MM-ddTHH:mm:ss.fffzz00"));
insert.Add("comment", comment);
insert.Add("timeSpentSeconds", timespentSeconds);

request.AddBody(insert);

var response = ExecuteRequest(request);
AssertStatus(response, HttpStatusCode.Created);

return deserializer.Deserialize<Worklog>(response);
}
catch (Exception ex)
{
Trace.TraceError("CreateComment(issue, comment) error: {0}", ex);
throw new JiraClientException("Could not create worklog", ex);
}
}

public Worklog UpdateWorklog(IssueRef issue, Worklog worklog)
{
try
{
var path = string.Format("issue/{0}/worklog/{1}", issue.id, worklog.id);
var request = CreateRequest(Method.PUT, path);
request.AddHeader("ContentType", "application/json");

var updateData = new Dictionary<string, object>();
if (worklog.comment != null) updateData.Add("comment", worklog.comment);
if (worklog.started != DateTime.MinValue) updateData.Add("started", worklog.started.ToString("yyyy-MM-ddTHH:mm:ss.fffzz00"));
if (worklog.timeSpentSeconds != 0) updateData.Add("timeSpentSeconds", worklog.timeSpentSeconds);
request.AddBody(updateData);

var response = ExecuteRequest(request);
AssertStatus(response, HttpStatusCode.OK);

return deserializer.Deserialize<Worklog>(response);
}
catch (Exception ex)
{
Trace.TraceError("UpdateWorklog(issue, worklog) error: {0}", ex);
throw new JiraClientException("Could not update worklog for issue", ex);
}
}

public void DeleteWorklog(IssueRef issue, Worklog worklog)
{
try
{
var path = String.Format("issue/{0}/worklog/{1}", issue.id, worklog.id);
var request = CreateRequest(Method.DELETE, path);

var response = ExecuteRequest(request);
AssertStatus(response, HttpStatusCode.NoContent);
}
catch (Exception ex)
{
Trace.TraceError("DeleteWorklog(issue, worklog) error: {0}", ex);
throw new JiraClientException("Could not delete worklog", ex);
}
}


public IEnumerable<Attachment> GetAttachments(IssueRef issue)
{
Expand Down Expand Up @@ -606,6 +743,28 @@ public IEnumerable<IssueType> GetIssueTypes()
}
}

public JiraUser GetUser(string username)
{
try
{
var path = string.Format("user?username={0}", username);
var request = CreateRequest(Method.POST, path);
request.AddHeader("ContentType", "application/json");

var response = ExecuteRequest(request);
AssertStatus(response, HttpStatusCode.OK);

var data = deserializer.Deserialize<JiraUser>(response);
return data;

}
catch (Exception ex)
{
Trace.TraceError("GetUser({0}) error: {1}", username, ex);
throw new JiraClientException("Could not load user", ex);
}
}

public ServerInfo GetServerInfo()
{
try
Expand Down
2 changes: 2 additions & 0 deletions TechTalk.JiraRestClient/JiraUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ namespace TechTalk.JiraRestClient
{
public class JiraUser
{
public string self { get; set; }
public string name { get; set; }
public string emailAddress { get; set; }
public string displayName { get; set; }
public bool active { get; set; }
public AvatarUrls avatarUrls { get; set; }
}
}
3 changes: 3 additions & 0 deletions TechTalk.JiraRestClient/TechTalk.JiraRestClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Attachment.cs" />
<Compile Include="AvatarUrls.cs" />
<Compile Include="Comment.cs" />
<Compile Include="CommentsContainer.cs" />
<Compile Include="Compatibility.cs" />
Expand All @@ -60,6 +61,8 @@
<Compile Include="Transition.cs" />
<Compile Include="Timetracking.cs" />
<Compile Include="TransitionsContainer.cs" />
<Compile Include="Worklog.cs" />
<Compile Include="WorklogsContainer.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config">
Expand Down
2 changes: 2 additions & 0 deletions TechTalk.JiraRestClient/Timetracking.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ public class Timetracking
{
public string originalEstimate { get; set; }
public int originalEstimateSeconds { get; set; }
public int remainingEstimateSeconds { get; set; }
public int timeSpentSeconds { get; set; }

private const decimal DayToSecFactor = 8 * 3600;
public decimal originalEstimateDays
Expand Down
16 changes: 16 additions & 0 deletions TechTalk.JiraRestClient/Worklog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;

namespace TechTalk.JiraRestClient
{
public class Worklog
{
public string id { get; set; }
public JiraUser author { get; set; }
public JiraUser updateAuthor { get; set; }
public string comment { get; set; }
public DateTime started { get; set; }
public DateTime created { get; set; }
public DateTime updated { get; set; }
public int timeSpentSeconds { get; set; }
}
}
12 changes: 12 additions & 0 deletions TechTalk.JiraRestClient/WorklogsContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Collections.Generic;

namespace TechTalk.JiraRestClient
{
internal class WorklogsContainer
{
public int startAt { get; set; }
public int maxResults { get; set; }
public int total { get; set; }
public List<Worklog> Worklogs { get; set; }
}
}