diff --git a/README.md b/README.md
index 68fb734b..c54f13d0 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,10 @@ The following API's are currently supported:
* [Projects-related methods](https://www.jetbrains.com/help/youtrack/standalone/Projects-Related-Methods.html) through `ProjectsService`
* [Issues-related methods](https://www.jetbrains.com/help/youtrack/standalone/Issues-Related-Methods.html) through `IssuesService`
* [Time-tracking-related methods](https://www.jetbrains.com/help/youtrack/standalone/Time-Tracking-User-Methods.html) through `TimeTrackingService`
-
+* Administration API's
+ * [User management](https://www.jetbrains.com/help/youtrack/standalone/Users.html) through `UserManagementService`
+ * [Time Tracker management](https://www.jetbrains.com/help/youtrack/standalone/Time-Tracking-Settings-Methods.html) through `TimeTrackingManagementService`
+
Many other API's are not included yet - feel free to [tackle one of the `UpForGrabs` issues](https://github.com/JetBrains/YouTrackSharp/issues?q=is%3Aissue+is%3Aopen+label%3AUpForGrabs) and make YouTrackSharp better!
diff --git a/build/YouTrackSharp.Build.csproj.dotsettings b/build/YouTrackSharp.Build.csproj.dotsettings
index 2ed93010..d62738ec 100644
--- a/build/YouTrackSharp.Build.csproj.dotsettings
+++ b/build/YouTrackSharp.Build.csproj.dotsettings
@@ -6,6 +6,7 @@
True
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ True
True
True
True
\ No newline at end of file
diff --git a/global.json b/global.json
index f992be3d..21340d61 100644
--- a/global.json
+++ b/global.json
@@ -1,5 +1 @@
-{
- "sdk": {
- "version": "1.0.4"
- }
-}
\ No newline at end of file
+{"sdk":{"version":"1.1.0"}}
\ No newline at end of file
diff --git a/src/YouTrackSharp/ConnectionExtensions.cs b/src/YouTrackSharp/ConnectionExtensions.cs
index 8a450c0a..6958afeb 100644
--- a/src/YouTrackSharp/ConnectionExtensions.cs
+++ b/src/YouTrackSharp/ConnectionExtensions.cs
@@ -1,11 +1,12 @@
using YouTrackSharp.Issues;
+using YouTrackSharp.Management;
using YouTrackSharp.Projects;
using YouTrackSharp.TimeTracking;
namespace YouTrackSharp
{
///
- /// Extension methods for , providing easy access to all services.
+ /// Extension methods for , providing easy access to all services.
///
public static class ConnectionExtensions
{
@@ -33,10 +34,30 @@ public static IssuesService CreateIssuesService(this Connection connection)
/// Creates a .
///
/// The to create a service with.
- /// for working with YouTrack issues.
+ /// for working with YouTrack time tracking.
public static TimeTrackingService CreateTimeTrackingService(this Connection connection)
{
return new TimeTrackingService(connection);
}
+
+ ///
+ /// Creates a .
+ ///
+ /// The to create a service with.
+ /// for managing YouTrack users.
+ public static UserManagementService CreateUserManagementService(this Connection connection)
+ {
+ return new UserManagementService(connection);
+ }
+
+ ///
+ /// Creates a .
+ ///
+ /// The to create a service with.
+ /// for managing YouTrack time tracking settings.
+ public static TimeTrackingManagementService CreateTimeTrackingManagementService(this Connection connection)
+ {
+ return new TimeTrackingManagementService(connection);
+ }
}
}
\ No newline at end of file
diff --git a/src/YouTrackSharp/Management/Group.cs b/src/YouTrackSharp/Management/Group.cs
new file mode 100644
index 00000000..1894ebdb
--- /dev/null
+++ b/src/YouTrackSharp/Management/Group.cs
@@ -0,0 +1,28 @@
+using Newtonsoft.Json;
+
+namespace YouTrackSharp.Management
+{
+ ///
+ /// A class that represents YouTrack group information.
+ ///
+ public class Group
+ {
+ ///
+ /// Id of the group.
+ ///
+ [JsonProperty("entityId")]
+ public string Id { get; set; }
+
+ ///
+ /// Name of the group.
+ ///
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ ///
+ /// URL of the group.
+ ///
+ [JsonProperty("url")]
+ public string Url { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/YouTrackSharp/Management/SystemWideTimeTrackingSettings.cs b/src/YouTrackSharp/Management/SystemWideTimeTrackingSettings.cs
new file mode 100644
index 00000000..c071ed11
--- /dev/null
+++ b/src/YouTrackSharp/Management/SystemWideTimeTrackingSettings.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace YouTrackSharp.Management
+{
+ ///
+ /// A class that represents YouTrack system wide time settings information.
+ ///
+ public class SystemWideTimeTrackingSettings
+ {
+ ///
+ /// Creates an instance of the class.
+ ///
+ public SystemWideTimeTrackingSettings()
+ {
+ WorkDays = new List>();
+ }
+
+ ///
+ /// Hours A Day.
+ ///
+ [JsonProperty("hoursADay")]
+ public int HoursADay { get; set; }
+
+ ///
+ /// Days A Week.
+ ///
+ [JsonProperty("daysAWeek")]
+ public int DaysAWeek { get; set; }
+
+ ///
+ /// WorkDays A Week.
+ ///
+ [JsonProperty("workWeek")]
+ public ICollection> WorkDays { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/YouTrackSharp/Management/TimeTrackingManagementService.cs b/src/YouTrackSharp/Management/TimeTrackingManagementService.cs
new file mode 100644
index 00000000..0d364214
--- /dev/null
+++ b/src/YouTrackSharp/Management/TimeTrackingManagementService.cs
@@ -0,0 +1,153 @@
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using YouTrackSharp.TimeTracking;
+
+namespace YouTrackSharp.Management
+{
+ ///
+ /// A class that represents a REST API client for administering Time Tracking Settings in YouTrack.
+ /// It uses a implementation to connect to the remote YouTrack server instance.
+ ///
+ public class TimeTrackingManagementService
+ {
+ private readonly Connection _connection;
+
+ ///
+ /// Creates an instance of the class.
+ ///
+ /// A instance that provides a connection to the remote YouTrack server instance.
+ public TimeTrackingManagementService(Connection connection)
+ {
+ _connection = connection ?? throw new ArgumentNullException(nameof(connection));
+ }
+
+ ///
+ /// Get the current system-wide time tracking settings.
+ ///
+ /// Uses the REST API Get System-wide Time Tracking Settings.
+ /// System-wide .
+ /// When the call to the remote YouTrack server instance failed.
+ public async Task GetSystemWideTimeTrackingSettings()
+ {
+ var client = await _connection.GetAuthenticatedHttpClient();
+ var response = await client.GetAsync($"rest/admin/timetracking");
+
+ response.EnsureSuccessStatusCode();
+
+ return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
+ }
+
+
+ ///
+ /// Updates the system-wide time tracking settings.
+ ///
+ /// Uses the REST API Set system-wide time tracking settings: a list of working days in a week, and a number of hours in a working day.
+ /// Parameter daysAWeek is ignored since Youtrack 5.1
+ /// When the call to the remote YouTrack server instance failed and YouTrack reported an error message.
+ /// When the call to the remote YouTrack server instance failed.
+ public async Task UpdateSystemWideTimeTrackingSettings(SystemWideTimeTrackingSettings timeSettings)
+ {
+ if (timeSettings == null)
+ {
+ throw new ArgumentNullException(nameof(timeSettings));
+ }
+
+ var stringContent = new StringContent(JsonConvert.SerializeObject(timeSettings));
+ stringContent.Headers.ContentType = new MediaTypeHeaderValue(Constants.HttpContentTypes.ApplicationJson);
+
+ var client = await _connection.GetAuthenticatedHttpClient();
+ var response = await client.PutAsync($"rest/admin/timetracking", stringContent);
+
+ if (response.StatusCode == HttpStatusCode.BadRequest)
+ {
+ // Try reading the error message
+ var responseJson = JObject.Parse(await response.Content.ReadAsStringAsync());
+ if (responseJson["value"] != null)
+ {
+ throw new YouTrackErrorException(responseJson["value"].Value());
+ }
+ else
+ {
+ throw new YouTrackErrorException(Strings.Exception_UnknownError);
+ }
+ }
+
+ response.EnsureSuccessStatusCode();
+ }
+
+
+ ///
+ /// Get the current time tracking settings for a specific project.
+ ///
+ /// Uses the REST API Get Time Tracking Settings for a Project.
+ /// Id of the project to get timetracking settings for.
+ /// .
+ /// When the is null or empty.
+ /// When the call to the remote YouTrack server instance failed.
+ public async Task GetTimeTrackingSettingsForProject(string projectId)
+ {
+ if (string.IsNullOrEmpty(projectId))
+ {
+ throw new ArgumentNullException(nameof(projectId));
+ }
+
+ var client = await _connection.GetAuthenticatedHttpClient();
+ var response = await client.GetAsync($"rest/admin/project/{projectId}/timetracking");
+
+ response.EnsureSuccessStatusCode();
+
+ return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
+ }
+
+
+ ///
+ /// Updates the current time tracking settings for a specific project.
+ ///
+ /// Uses the REST API Configure time tracking settings for a specific project.
+ /// Id of the project to update.
+ /// Timetracking settings for this project.
+ /// When the is null or empty.
+ /// When the is null.
+ /// When the call to the remote YouTrack server instance failed and YouTrack reported an error message.
+ /// When the call to the remote YouTrack server instance failed.
+ public async Task UpdateTimeTrackingSettingsForProject(string projectId, TimeTrackingSettings timeTrackingSettings)
+ {
+ if (string.IsNullOrEmpty(projectId))
+ {
+ throw new ArgumentNullException(nameof(projectId));
+ }
+
+ if (timeTrackingSettings == null)
+ {
+ throw new ArgumentNullException(nameof(timeTrackingSettings));
+ }
+
+ var stringContent = new StringContent(JsonConvert.SerializeObject(timeTrackingSettings));
+ stringContent.Headers.ContentType = new MediaTypeHeaderValue(Constants.HttpContentTypes.ApplicationJson);
+
+ var client = await _connection.GetAuthenticatedHttpClient();
+ var response = await client.PutAsync($"rest/admin/project/{projectId}/timetracking", stringContent);
+
+ if (response.StatusCode == HttpStatusCode.BadRequest)
+ {
+ // Try reading the error message
+ var responseJson = JObject.Parse(await response.Content.ReadAsStringAsync());
+ if (responseJson["value"] != null)
+ {
+ throw new YouTrackErrorException(responseJson["value"].Value());
+ }
+ else
+ {
+ throw new YouTrackErrorException(Strings.Exception_UnknownError);
+ }
+ }
+
+ response.EnsureSuccessStatusCode();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/YouTrackSharp/Management/TimeTrackingSettings.cs b/src/YouTrackSharp/Management/TimeTrackingSettings.cs
new file mode 100644
index 00000000..8245b582
--- /dev/null
+++ b/src/YouTrackSharp/Management/TimeTrackingSettings.cs
@@ -0,0 +1,43 @@
+using Newtonsoft.Json;
+
+namespace YouTrackSharp.Management
+{
+ ///
+ /// A class that represents YouTrack timetracking settings information.
+ ///
+ public class TimeTrackingSettings
+ {
+ ///
+ /// Is time tracking enabled?
+ ///
+ [JsonProperty("enabled")]
+ public bool Enabled { get; set; }
+
+ ///
+ /// Field that contains Estimation data.
+ ///
+ [JsonProperty("estimation")]
+ public TimeField Estimation { get; set; }
+
+ ///
+ /// Field that contains SpentTime data.
+ ///
+ [JsonProperty("spentTime")]
+ public TimeField SpentTime { get; set; }
+
+ public class TimeField
+ {
+ ///
+ /// Name of the field.
+ ///
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ ///
+ /// Url of the field.
+ ///
+ [JsonProperty("url")]
+ public string Url { get; set; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/YouTrackSharp/Management/User.cs b/src/YouTrackSharp/Management/User.cs
new file mode 100644
index 00000000..d3554ac9
--- /dev/null
+++ b/src/YouTrackSharp/Management/User.cs
@@ -0,0 +1,34 @@
+using Newtonsoft.Json;
+
+namespace YouTrackSharp.Management
+{
+ ///
+ /// A class that represents YouTrack user information.
+ ///
+ public class User
+ {
+ ///
+ /// Username of the user.
+ ///
+ [JsonProperty("login")]
+ public string Username { get; set; }
+
+ ///
+ /// Full name of the user.
+ ///
+ [JsonProperty("fullName")]
+ public string FullName { get; set; }
+
+ ///
+ /// Email address of the user.
+ ///
+ [JsonProperty("email")]
+ public string Email { get; set; }
+
+ ///
+ /// Jabber of the user.
+ ///
+ [JsonProperty("jabber")]
+ public string Jabber { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/YouTrackSharp/Management/UserManagementService.cs b/src/YouTrackSharp/Management/UserManagementService.cs
new file mode 100644
index 00000000..030a7d80
--- /dev/null
+++ b/src/YouTrackSharp/Management/UserManagementService.cs
@@ -0,0 +1,271 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace YouTrackSharp.Management
+{
+ ///
+ /// A class that represents a REST API client for administering user accounts in YouTrack.
+ /// It uses a implementation to connect to the remote YouTrack server instance.
+ ///
+ public class UserManagementService
+ {
+ private readonly Connection _connection;
+
+ ///
+ /// Creates an instance of the class.
+ ///
+ /// A instance that provides a connection to the remote YouTrack server instance.
+ public UserManagementService(Connection connection)
+ {
+ _connection = connection ?? throw new ArgumentNullException(nameof(connection));
+ }
+
+ ///
+ /// Get user by login name.
+ ///
+ /// Uses the REST API Get user by login name.
+ /// A instance that represents the user matching or null if the user does not exist.
+ /// When the call to the remote YouTrack server instance failed.
+ /// When the is null or empty.
+ public async Task GetUser(string username)
+ {
+ if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username));
+
+ var client = await _connection.GetAuthenticatedHttpClient();
+ var response = await client.GetAsync($"rest/admin/user/{username}");
+
+ if (response.StatusCode == HttpStatusCode.NotFound)
+ {
+ return null;
+ }
+
+ response.EnsureSuccessStatusCode();
+
+ return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
+ }
+
+ ///
+ /// Get a list of all available registered users.
+ ///
+ /// Uses the REST API Get a list of all available registered users.
+ /// Search query (part of user login, name or email).
+ /// Filter by group (groupID).
+ /// Filter by role.
+ /// Filter by project (projectID)
+ /// Filter by permission.
+ /// When true, get only users which are currently online. Defaults to false.
+ /// Paginator mode (takes 10 records).
+ /// A of instances.
+ /// When the call to the remote YouTrack server instance failed.
+ public async Task> GetUsers(string filter = null, string group = null, string role = null,
+ string project = null, string permission = null, bool onlineOnly = false, int start = 0)
+ {
+ var queryString = new List(7);
+ if (!string.IsNullOrEmpty(filter))
+ {
+ queryString.Add($"q={WebUtility.UrlEncode(filter)}");
+ }
+ if (!string.IsNullOrEmpty(group))
+ {
+ queryString.Add($"group={WebUtility.UrlEncode(group)}");
+ }
+ if (!string.IsNullOrEmpty(role))
+ {
+ queryString.Add($"role={WebUtility.UrlEncode(role)}");
+ }
+ if (!string.IsNullOrEmpty(project))
+ {
+ queryString.Add($"project={WebUtility.UrlEncode(project)}");
+ }
+ if (!string.IsNullOrEmpty(permission))
+ {
+ queryString.Add($"permission={WebUtility.UrlEncode(permission)}");
+ }
+ if (onlineOnly)
+ {
+ queryString.Add($"onlineOnly=true");
+ }
+ if (start > 0)
+ {
+ queryString.Add($"start={start}");
+ }
+
+ var query = string.Join("&", queryString);
+
+ return await GetUsersFromPath($"rest/admin/user?{query}");
+ }
+
+ ///
+ /// Create a new user.
+ ///
+ /// Uses the REST API Create a new user.
+ /// Login name of the user to be created.
+ /// Full name of a new user.
+ /// E-mail address of the user.
+ /// Jabber address for the new user.
+ /// Password for the new user.
+ /// When the call to the remote YouTrack server instance failed.
+ public async Task CreateUser(string username, string fullName, string email, string jabber, string password)
+ {
+ var queryString = new Dictionary(4);
+ if (!string.IsNullOrEmpty(fullName))
+ {
+ queryString.Add("fullName", WebUtility.UrlEncode(fullName));
+ }
+ if (!string.IsNullOrEmpty(email))
+ {
+ queryString.Add("email", WebUtility.UrlEncode(email));
+ }
+ if (!string.IsNullOrEmpty(jabber))
+ {
+ queryString.Add("jabber", WebUtility.UrlEncode(jabber));
+ }
+ if (!string.IsNullOrEmpty(password))
+ {
+ queryString.Add("password", WebUtility.UrlEncode(password));
+ }
+
+ var client = await _connection.GetAuthenticatedHttpClient();
+ var response = await client.PutAsync($"rest/admin/user/{username}", new FormUrlEncodedContent(queryString));
+
+ response.EnsureSuccessStatusCode();
+ }
+
+ ///
+ /// Updates a user.
+ ///
+ /// Uses the REST API Update a user.
+ /// Login name of the user to be updated.
+ /// Full name of a user.
+ /// E-mail address of the user.
+ /// Jabber address for the user.
+ /// Password for the user.
+ /// When the call to the remote YouTrack server instance failed.
+ public async Task UpdateUser(string username, string fullName = null, string email = null, string jabber = null, string password = null)
+ {
+ var queryString = new Dictionary(4);
+ if (!string.IsNullOrEmpty(fullName))
+ {
+ queryString.Add("fullName", WebUtility.UrlEncode(fullName));
+ }
+ if (!string.IsNullOrEmpty(email))
+ {
+ queryString.Add("email", WebUtility.UrlEncode(email));
+ }
+ if (!string.IsNullOrEmpty(jabber))
+ {
+ queryString.Add("jabber", WebUtility.UrlEncode(jabber));
+ }
+ if (!string.IsNullOrEmpty(password))
+ {
+ queryString.Add("password", WebUtility.UrlEncode(password));
+ }
+
+ var client = await _connection.GetAuthenticatedHttpClient();
+ var response = await client.PostAsync($"rest/admin/user/{username}", new FormUrlEncodedContent(queryString));
+
+ response.EnsureSuccessStatusCode();
+ }
+
+ ///
+ /// Delete specific user account.
+ ///
+ /// Uses the REST API Delete a user.
+ /// Login name of the user to be deleted.
+ /// When the call to the remote YouTrack server instance failed.
+ public async Task DeleteUser(string username)
+ {
+ var client = await _connection.GetAuthenticatedHttpClient();
+ var response = await client.DeleteAsync($"rest/admin/user/{username}");
+
+ response.EnsureSuccessStatusCode();
+ }
+
+ ///
+ /// Merge users.
+ ///
+ /// Uses the REST API Delete a user.
+ /// Login name of the user to be merged.
+ /// Login name of the user to merge into.
+ /// When the call to the remote YouTrack server instance failed.
+ public async Task MergeUsers(string usernameToMerge, string targetUser)
+ {
+ var client = await _connection.GetAuthenticatedHttpClient();
+ var response = await client.PostAsync($"rest/admin/user/{targetUser}/merge/{usernameToMerge}", new StringContent(string.Empty));
+
+ response.EnsureSuccessStatusCode();
+ }
+
+ ///
+ /// Get all groups the specified user participates in.
+ ///
+ /// Uses the REST API Get all groups the specified user participates in.
+ /// Login name of the user to retrieve information for.
+ /// A of instances.
+ /// When the call to the remote YouTrack server instance failed.
+ public async Task> GetGroupsForUser(string username)
+ {
+ var client = await _connection.GetAuthenticatedHttpClient();
+ var response = await client.GetAsync($"rest/admin/user/{username}/group");
+
+ response.EnsureSuccessStatusCode();
+
+ return JsonConvert.DeserializeObject>(
+ await response.Content.ReadAsStringAsync());
+ }
+
+ ///
+ /// Add user to group.
+ ///
+ /// Uses the REST API Add user account to a group.
+ /// Login name of the user to be updated.
+ /// Name of the group to add the user to.
+ /// When the call to the remote YouTrack server instance failed.
+ public async Task AddUserToGroup(string username, string group)
+ {
+ var client = await _connection.GetAuthenticatedHttpClient();
+ var response = await client.PostAsync($"rest/admin/user/{username}/group/{WebUtility.UrlEncode(group)}", new StringContent(string.Empty));
+
+ response.EnsureSuccessStatusCode();
+ }
+
+ ///
+ /// Remove user from group.
+ ///
+ /// Uses the REST API Remove user account from a group.
+ /// Login name of the user to be updated.
+ /// Name of the group to remove the user from.
+ /// When the call to the remote YouTrack server instance failed.
+ public async Task RemoveUserFromGroup(string username, string group)
+ {
+ var client = await _connection.GetAuthenticatedHttpClient();
+ var response = await client.DeleteAsync($"rest/admin/user/{username}/group/{WebUtility.UrlEncode(group)}");
+
+ response.EnsureSuccessStatusCode();
+ }
+
+ private async Task> GetUsersFromPath(string path)
+ {
+ if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path));
+
+ var client = await _connection.GetAuthenticatedHttpClient();
+ var response = await client.GetAsync(path);
+
+ response.EnsureSuccessStatusCode();
+
+ var users = new List();
+ var userRefs = JArray.Parse(await response.Content.ReadAsStringAsync());
+ foreach (var userRef in userRefs)
+ {
+ users.Add(await GetUser(userRef["login"].Value()));
+ }
+
+ return users;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/YouTrackSharp/TimeTracking/TimeTrackingService.cs b/src/YouTrackSharp/TimeTracking/TimeTrackingService.cs
index 74a84c99..b768cfba 100644
--- a/src/YouTrackSharp/TimeTracking/TimeTrackingService.cs
+++ b/src/YouTrackSharp/TimeTracking/TimeTrackingService.cs
@@ -170,7 +170,6 @@ public async Task UpdateWorkItemForIssue(string issueId, string workItemId, Work
response.EnsureSuccessStatusCode();
}
-
///
/// Deletes a work item for an issue from the server.
///
@@ -196,5 +195,5 @@ public async Task DeleteWorkItemForIssue(string issueId, string workItemId)
response.EnsureSuccessStatusCode();
}
- }
+ }
}
\ No newline at end of file
diff --git a/src/YouTrackSharp/TimeTracking/WorkItem.cs b/src/YouTrackSharp/TimeTracking/WorkItem.cs
index 9b4b0e3f..0bfeed85 100644
--- a/src/YouTrackSharp/TimeTracking/WorkItem.cs
+++ b/src/YouTrackSharp/TimeTracking/WorkItem.cs
@@ -42,7 +42,7 @@ public WorkItem(DateTime? date, TimeSpan duration, string description = null, Wo
public string Id { get; set; }
///
- /// Represents when the work item was created.
+ /// Represents when the work item was performed.
///
[JsonConverter(typeof(UnixDateTimeOffsetConverter))]
[JsonProperty("date")]
@@ -72,5 +72,19 @@ public WorkItem(DateTime? date, TimeSpan duration, string description = null, Wo
///
[JsonProperty("author")]
public Author Author { get; set; }
+
+ ///
+ /// Represents when the work item was created.
+ ///
+ [JsonConverter(typeof(UnixDateTimeOffsetConverter))]
+ [JsonProperty("created")]
+ public DateTime? Created { get; set; }
+
+ ///
+ /// Represents when the work item was updated.
+ ///
+ [JsonConverter(typeof(UnixDateTimeOffsetConverter))]
+ [JsonProperty("updated")]
+ public DateTime? Updated { get; set; }
}
}
\ No newline at end of file
diff --git a/src/YouTrackSharp/YouTrackSharp.csproj b/src/YouTrackSharp/YouTrackSharp.csproj
index dbcf7354..503c8a55 100644
--- a/src/YouTrackSharp/YouTrackSharp.csproj
+++ b/src/YouTrackSharp/YouTrackSharp.csproj
@@ -5,7 +5,7 @@
True
True
https://raw.githubusercontent.com/JetBrains/YouTrackSharp/master/logo.png
- https://github.com/JetBrains/YouTrackSharp/blob/master/LICENSE.TXT
+ https://github.com/JetBrains/YouTrackSharp/blob/master/LICENSE.txt
https://github.com/JetBrains/YouTrackSharp/
YouTrack JetBrains SDK
3.0.0.0
@@ -17,7 +17,7 @@
JetBrains
https://github.com/JetBrains/YouTrackSharp.git
Git
- 3.0.1
+ 3.1.0
diff --git a/tests/YouTrackSharp.Tests/Infrastructure/Connections.cs b/tests/YouTrackSharp.Tests/Infrastructure/Connections.cs
index a602da75..613c6fa4 100644
--- a/tests/YouTrackSharp.Tests/Infrastructure/Connections.cs
+++ b/tests/YouTrackSharp.Tests/Infrastructure/Connections.cs
@@ -19,6 +19,8 @@ public static string ServerUrl
public static Connection Demo2Password =>
new UsernamePasswordConnection(ServerUrl, "demo2", "demo2");
+ public static Connection Demo3Token =>
+ new BearerTokenConnection(ServerUrl, "perm:ZGVtbzM=.WW91VHJhY2tTaGFycA==.L04RdcCnjyW2UPCVg1qyb6dQflpzFy");
public static class TestData
{
diff --git a/tests/YouTrackSharp.Tests/Integration/Management/TimeTracking/GetSystemWideTimeTrackingSettings.cs b/tests/YouTrackSharp.Tests/Integration/Management/TimeTracking/GetSystemWideTimeTrackingSettings.cs
new file mode 100644
index 00000000..102a97cc
--- /dev/null
+++ b/tests/YouTrackSharp.Tests/Integration/Management/TimeTracking/GetSystemWideTimeTrackingSettings.cs
@@ -0,0 +1,35 @@
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+using YouTrackSharp.Tests.Infrastructure;
+
+namespace YouTrackSharp.Tests.Integration.Management.TimeTracking
+{
+ public partial class TimeTrackingServiceTests
+ {
+ public class GetSystemwideTimeTrackingSettings
+ {
+ [Fact]
+ public async Task Valid_Connection_Gets_Systemwide_TimeTracking_Settings()
+ {
+ // Arrange
+ var connection = Connections.Demo3Token;
+ var service = connection.CreateTimeTrackingManagementService();
+
+ // Act
+ var results = await service.GetSystemWideTimeTrackingSettings();
+ var workdays = results.WorkDays.ToList();
+
+ // Assert
+ Assert.Equal(5, results.DaysAWeek);
+ Assert.True(results.HoursADay > 0);
+
+ Assert.Equal(1, workdays[0].Value);
+ Assert.Equal(2, workdays[1].Value);
+ Assert.Equal(3, workdays[2].Value);
+ Assert.Equal(4, workdays[3].Value);
+ Assert.Equal(5, workdays[4].Value);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/YouTrackSharp.Tests/Integration/Management/TimeTracking/GetTimeTrackingSettingsForProject.cs b/tests/YouTrackSharp.Tests/Integration/Management/TimeTracking/GetTimeTrackingSettingsForProject.cs
new file mode 100644
index 00000000..7f90870a
--- /dev/null
+++ b/tests/YouTrackSharp.Tests/Integration/Management/TimeTracking/GetTimeTrackingSettingsForProject.cs
@@ -0,0 +1,28 @@
+using System.Threading.Tasks;
+using Xunit;
+using YouTrackSharp.Tests.Infrastructure;
+
+namespace YouTrackSharp.Tests.Integration.Management.TimeTracking
+{
+ public partial class TimeTrackingServiceTests
+ {
+ public class GetTimeTrackingSettingsForProject
+ {
+ [Fact]
+ public async Task Valid_Connection_Gets_TimeTracking_Settings_For_Project()
+ {
+ // Arrange
+ var connection = Connections.Demo3Token;
+ var service = connection.CreateTimeTrackingManagementService();
+
+ // Act
+ var results = await service.GetTimeTrackingSettingsForProject("DP1");
+
+ // Assert
+ Assert.True(results.Enabled);
+ Assert.Equal("Estimation", results.Estimation.Name);
+ Assert.Equal("Spent time", results.SpentTime.Name);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/YouTrackSharp.Tests/Integration/Management/TimeTracking/UpdateSystemWideTimeTrackingSettings.cs b/tests/YouTrackSharp.Tests/Integration/Management/TimeTracking/UpdateSystemWideTimeTrackingSettings.cs
new file mode 100644
index 00000000..8116a013
--- /dev/null
+++ b/tests/YouTrackSharp.Tests/Integration/Management/TimeTracking/UpdateSystemWideTimeTrackingSettings.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Xunit;
+using YouTrackSharp.Management;
+using YouTrackSharp.Tests.Infrastructure;
+
+namespace YouTrackSharp.Tests.Integration.Management.TimeTracking
+{
+ public partial class TimeTrackingManagementServiceTests
+ {
+ public class UpdateSystemWideTimeTrackingSettings
+ {
+ [Fact]
+ public async Task Valid_Connection_Updates_Systemwide_TimeTracking_Settings()
+ {
+ // Arrange
+ var connection = Connections.Demo3Token;
+ var service = connection.CreateTimeTrackingManagementService();
+
+ var random = new Random();
+ var hoursADay = random.Next(1, 20);
+
+ // Act
+ var timeSettings = new SystemWideTimeTrackingSettings
+ {
+ HoursADay = hoursADay,
+ WorkDays = new List>(5)
+ };
+ timeSettings.WorkDays.Add(new SubValue { Value = 1 });
+ timeSettings.WorkDays.Add(new SubValue { Value = 2 });
+ timeSettings.WorkDays.Add(new SubValue { Value = 3 });
+ timeSettings.WorkDays.Add(new SubValue { Value = 4 });
+ timeSettings.WorkDays.Add(new SubValue { Value = 5 });
+
+ await service.UpdateSystemWideTimeTrackingSettings(timeSettings);
+
+ // Assert
+ var result = await service.GetSystemWideTimeTrackingSettings();
+ Assert.NotNull(result);
+ Assert.Equal(hoursADay, result.HoursADay);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/YouTrackSharp.Tests/Integration/Management/TimeTracking/UpdateTimeTrackingSettingsForProject.cs b/tests/YouTrackSharp.Tests/Integration/Management/TimeTracking/UpdateTimeTrackingSettingsForProject.cs
new file mode 100644
index 00000000..adbff4c4
--- /dev/null
+++ b/tests/YouTrackSharp.Tests/Integration/Management/TimeTracking/UpdateTimeTrackingSettingsForProject.cs
@@ -0,0 +1,28 @@
+using System.Threading.Tasks;
+using Xunit;
+using YouTrackSharp.Management;
+using YouTrackSharp.Tests.Infrastructure;
+
+namespace YouTrackSharp.Tests.Integration.Management.TimeTracking
+{
+ public partial class TimeTrackingServiceTests
+ {
+ public class TimeTrackingManagementServiceTests
+ {
+ [Fact]
+ public async Task Valid_Connection_Updates_TimeTracking_Settings_For_Project()
+ {
+ // Arrange
+ var connection = Connections.Demo1Token;
+ var service = connection.CreateTimeTrackingManagementService();
+
+ // Act
+ await service.UpdateTimeTrackingSettingsForProject("DP1", new TimeTrackingSettings { Enabled = true });
+
+ // Assert
+ var result = await service.GetTimeTrackingSettingsForProject("DP1");
+ Assert.True(result.Enabled);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/AddAndRemoveUserFromGroup.cs b/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/AddAndRemoveUserFromGroup.cs
new file mode 100644
index 00000000..866f7614
--- /dev/null
+++ b/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/AddAndRemoveUserFromGroup.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+using YouTrackSharp.Tests.Infrastructure;
+
+namespace YouTrackSharp.Tests.Integration.Management.UserManagement
+{
+ public partial class UserManagementServiceTests
+ {
+ public class AddAndRemoveUserFromGroup
+ {
+ [Fact]
+ public async Task Valid_Connection_Adds_And_Removes_User_From_Group()
+ {
+ // Arrange
+ var connection = Connections.Demo3Token;
+ var service = connection.CreateUserManagementService();
+
+ // Act
+ try
+ {
+ await service.AddUserToGroup("demo2", "Reporters");
+
+ // Assert
+ var result = await service.GetGroupsForUser("demo2");
+ Assert.NotNull(result);
+ Assert.True(result.Count > 0);
+ Assert.True(result.Any(group =>
+ string.Equals(group.Name, "Reporters", StringComparison.OrdinalIgnoreCase)));
+ }
+ finally
+ {
+ await service.RemoveUserFromGroup("demo2", "Reporters");
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/CreateAndDeleteUser.cs b/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/CreateAndDeleteUser.cs
new file mode 100644
index 00000000..edac4518
--- /dev/null
+++ b/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/CreateAndDeleteUser.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Threading.Tasks;
+using Xunit;
+using YouTrackSharp.Tests.Infrastructure;
+
+namespace YouTrackSharp.Tests.Integration.Management.UserManagement
+{
+ public partial class UserManagementServiceTests
+ {
+ public class CreateAndDeleteUser
+ {
+ [Fact(Skip = "Don't want to pollute our server instance with random users.")]
+ public async Task Valid_Connection_Creates_User()
+ {
+ // Arrange
+ var connection = Connections.Demo3Token;
+ var service = connection.CreateUserManagementService();
+
+ var randomUsername = "test" + Guid.NewGuid().ToString().Replace("-", string.Empty);
+ var randomPassword = "pwd" + Guid.NewGuid().ToString().Replace("-", string.Empty);
+
+ // Act
+ await service.CreateUser(randomUsername, "Test User", "test@example.org", null, randomPassword);
+
+ // Assert
+ try
+ {
+ var result = await service.GetUser(randomUsername);
+ Assert.NotNull(result);
+ Assert.Equal(randomUsername, result.Username);
+ }
+ finally
+ {
+ // Delete the user
+ await service.DeleteUser(randomUsername);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/CreateAndMergeUser.cs b/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/CreateAndMergeUser.cs
new file mode 100644
index 00000000..7b9b5f09
--- /dev/null
+++ b/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/CreateAndMergeUser.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Threading.Tasks;
+using Xunit;
+using YouTrackSharp.Tests.Infrastructure;
+
+namespace YouTrackSharp.Tests.Integration.Management.UserManagement
+{
+ public partial class UserManagementServiceTests
+ {
+ public class CreateAndMergeUser
+ {
+ [Fact(Skip = "Don't want to pollute our server instance with random users.")]
+ public async Task Valid_Connection_Merges_User()
+ {
+ // Arrange
+ var connection = Connections.Demo3Token;
+ var service = connection.CreateUserManagementService();
+
+ var randomUsername = "test" + Guid.NewGuid().ToString().Replace("-", string.Empty);
+ var randomPassword = "pwd" + Guid.NewGuid().ToString().Replace("-", string.Empty);
+
+ await service.CreateUser(randomUsername + "1", "Test 1 User", "test1@example.org", null, randomPassword);
+ await service.CreateUser(randomUsername + "2", "Test 2 User", "test2@example.org", null, randomPassword);
+
+ // Act
+ await service.MergeUsers(randomUsername + "1", randomUsername + "2");
+
+ // Assert
+ try
+ {
+ var result1 = await service.GetUser(randomUsername + "1");
+ var result2 = await service.GetUser(randomUsername + "2");
+
+ Assert.Null(result1);
+
+ Assert.NotNull(result2);
+ Assert.Equal(randomUsername + "2", result2.Username);
+ }
+ finally
+ {
+ // Delete the user
+ await service.DeleteUser(randomUsername + "2");
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/CreateAndUpdateUser.cs b/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/CreateAndUpdateUser.cs
new file mode 100644
index 00000000..9054f136
--- /dev/null
+++ b/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/CreateAndUpdateUser.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Threading.Tasks;
+using Xunit;
+using YouTrackSharp.Tests.Infrastructure;
+
+namespace YouTrackSharp.Tests.Integration.Management.UserManagement
+{
+ public partial class UserManagementServiceTests
+ {
+ public class CreateAndUpdateUser
+ {
+ [Fact(Skip = "Don't want to pollute our server instance with random users.")]
+ public async Task Valid_Connection_Updates_User()
+ {
+ // Arrange
+ var connection = Connections.Demo3Token;
+ var service = connection.CreateUserManagementService();
+
+ var randomUsername = "test" + Guid.NewGuid().ToString().Replace("-", string.Empty);
+ var randomPassword = "pwd" + Guid.NewGuid().ToString().Replace("-", string.Empty);
+
+ await service.CreateUser(randomUsername, "Test User", "test1@example.org", null, randomPassword);
+
+ // Act
+ await service.UpdateUser(randomUsername, "Test user (updated)");
+
+ // Assert
+ try
+ {
+ var result = await service.GetUser(randomUsername);
+
+ Assert.NotNull(result);
+ Assert.Equal(randomUsername, "Test user (updated)");
+ }
+ finally
+ {
+ // Delete the user
+ await service.DeleteUser(randomUsername);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/GetGroupsForUser.cs b/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/GetGroupsForUser.cs
new file mode 100644
index 00000000..9994b19e
--- /dev/null
+++ b/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/GetGroupsForUser.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+using YouTrackSharp.Tests.Infrastructure;
+
+namespace YouTrackSharp.Tests.Integration.Management.UserManagement
+{
+ public partial class UserManagementServiceTests
+ {
+ public class GetGroupsForUser
+ {
+ [Fact]
+ public async Task Valid_Connection_Returns_Groups_ForUser()
+ {
+ // Arrange
+ var connection = Connections.Demo3Token;
+ var service = connection.CreateUserManagementService();
+
+ // Act
+ var result = await service.GetGroupsForUser("demo3");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.True(result.Count > 0);
+ Assert.True(result.Any(group =>
+ string.Equals(group.Name, "All Users", StringComparison.OrdinalIgnoreCase)));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/GetUser.cs b/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/GetUser.cs
new file mode 100644
index 00000000..48170873
--- /dev/null
+++ b/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/GetUser.cs
@@ -0,0 +1,41 @@
+using System.Threading.Tasks;
+using Xunit;
+using YouTrackSharp.Tests.Infrastructure;
+
+namespace YouTrackSharp.Tests.Integration.Management.UserManagement
+{
+ public partial class UserManagementServiceTests
+ {
+ public class GetUser
+ {
+ [Fact]
+ public async Task Valid_Connection_Returns_User()
+ {
+ // Arrange
+ var connection = Connections.Demo3Token;
+ var service = connection.CreateUserManagementService();
+
+ // Act
+ var result = await service.GetUser("demo1");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("demo1", result.Username);
+ }
+
+ [Fact]
+ public async Task Valid_Connection_Returns_Null_For_NonExistant_User()
+ {
+ // Arrange
+ var connection = Connections.Demo3Token;
+ var service = connection.CreateUserManagementService();
+
+ // Act
+ var result = await service.GetUser("does-not-exist");
+
+ // Assert
+ Assert.Null(result);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/GetUsers.cs b/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/GetUsers.cs
new file mode 100644
index 00000000..1cadbaa1
--- /dev/null
+++ b/tests/YouTrackSharp.Tests/Integration/Management/UserManagement/GetUsers.cs
@@ -0,0 +1,29 @@
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+using YouTrackSharp.Tests.Infrastructure;
+
+namespace YouTrackSharp.Tests.Integration.Management.UserManagement
+{
+ public partial class UserManagementServiceTests
+ {
+ public class GetUsers
+ {
+ [Fact]
+ public async Task Valid_Connection_Returns_Users()
+ {
+ // Arrange
+ var connection = Connections.Demo3Token;
+ var service = connection.CreateUserManagementService();
+
+ // Act
+ var result = await service.GetUsers();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.True(result.Count > 0);
+ Assert.True(result.Any(user => user.Username == "demo1"));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/YouTrackSharp.Tests/YouTrackSharp.Tests.csproj b/tests/YouTrackSharp.Tests/YouTrackSharp.Tests.csproj
index e0ba89cd..98e647e0 100644
--- a/tests/YouTrackSharp.Tests/YouTrackSharp.Tests.csproj
+++ b/tests/YouTrackSharp.Tests/YouTrackSharp.Tests.csproj
@@ -7,13 +7,13 @@
JetBrains
YouTrackSharp
Tests for YouTrackSharp.
- https://github.com/JetBrains/YouTrackSharp/blob/master/LICENSE.TXT
+ https://github.com/JetBrains/YouTrackSharp/blob/master/LICENSE.txt
JetBrains
https://github.com/JetBrains/YouTrackSharp/
https://raw.githubusercontent.com/JetBrains/YouTrackSharp/master/logo.png
https://github.com/JetBrains/YouTrackSharp.git
Git
- 3.0.1
+ 3.1.0
False
diff --git a/tests/YouTrackSharp.Tests/YouTrackSharp.Tests.csproj.DotSettings b/tests/YouTrackSharp.Tests/YouTrackSharp.Tests.csproj.DotSettings
new file mode 100644
index 00000000..e303c506
--- /dev/null
+++ b/tests/YouTrackSharp.Tests/YouTrackSharp.Tests.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ False
\ No newline at end of file