Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

https://github.com/JetBrains/YouTrackSharp/issues/40 #69

Merged
Merged
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
10 changes: 10 additions & 0 deletions src/YouTrackSharp/ConnectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,15 @@ public static TimeTrackingManagementService CreateTimeTrackingManagementService(
{
return new TimeTrackingManagementService(connection);
}

/// <summary>
/// Creates a <see cref="ProjectCustomFieldsService" />.
/// </summary>
/// <param name="connection">The <see cref="Connection" /> to create a service with.</param>
/// <returns><see cref="ProjectCustomFieldsService" /> for accessing custom project fields.</returns>
public static ProjectCustomFieldsService ProjectCustomFieldsService(this Connection connection)
{
return new ProjectCustomFieldsService(connection);
}
}
}
52 changes: 52 additions & 0 deletions src/YouTrackSharp/Projects/CustomField.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Newtonsoft.Json;

namespace YouTrackSharp.Projects
{
/// <summary>
/// Custom field for a project
/// </summary>
public class CustomField
{
/// <summary>
/// Creates an instance of the <see cref="CustomField" /> class.
/// </summary>
public CustomField()
{
Name = string.Empty;
Url = string.Empty;
Type = string.Empty;
CanBeEmpty = false;
EmptyText = string.Empty;
}

/// <summary>
/// Name of project custom field.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }

/// <summary>
/// The Url of the custom field.
/// </summary>
[JsonProperty("url")]
public string Url { get; set; }

/// <summary>
/// Type of this custom field.
/// </summary>
[JsonProperty("type")]
public string Type { get; set; }

/// <summary>
/// Mandatory binary parameter defining if the field can have empty value or not.
/// </summary>
[JsonProperty("canBeEmpty")]
public bool CanBeEmpty { get; set; }

/// <summary>
/// Text that is shown when the custom field has no value.
/// </summary>
[JsonProperty("emptyText")]
public string EmptyText { get; set; }
}
}
159 changes: 159 additions & 0 deletions src/YouTrackSharp/Projects/ProjectCustomFieldsService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
using System;
using System.Collections.Generic;
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.Management;

namespace YouTrackSharp.Projects
{
/// <summary>
/// A class that represents a REST API client for <a href="https://www.jetbrains.com/help/youtrack/standalone/Project-Custom-Fields.html"> methods related to operations with custom fields of a project</a>.
/// It uses a <see cref="Connection" /> implementation to connect to the remote YouTrack server instance.
/// </summary>
public class ProjectCustomFieldsService
{
private readonly Connection _connection;

/// <summary>
/// Creates an instance of the <see cref="ProjectCustomFieldsService" /> class.
/// </summary>
/// <param name="connection">A <see cref="Connection" /> instance that provides a connection to the remote YouTrack server instance.</param>
public ProjectCustomFieldsService(Connection connection)
{
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
}

/// <summary>
/// Get custom fields used in a project.
/// </summary>
/// <remarks>Uses the REST API <a href="https://www.jetbrains.com/help/youtrack/standalone/GET-Project-Custom-Fields.html">Get Project Custom Fields</a>.</remarks>
/// <param name="projectId">Id of the project to get the custom fields for.</param>
/// <returns>A <see cref="T:System.Collections.Generic.ICollection`1" /> of <see cref="CustomField" /> that are accessible for currently logged in user.</returns>
/// <exception cref="T:System.Net.HttpRequestException">When the call to the remote YouTrack server instance failed.</exception>
public async Task<ICollection<CustomField>> GetProjectCustomFields(string projectId)
{
var client = await _connection.GetAuthenticatedHttpClient();
var response = await client.GetAsync($"rest/admin/project/{projectId}/customfield");

response.EnsureSuccessStatusCode();

return JsonConvert.DeserializeObject<ICollection<CustomField>>(await response.Content.ReadAsStringAsync());
}

/// <summary>
/// Get a project's custom field by its name.
/// </summary>
/// <remarks>Uses the REST API <a href="https://www.jetbrains.com/help/youtrack/standalone/GET-Project-Custom-Field.html">Get Project Custom Field</a>.</remarks>
/// <param name="projectId">Id of the project to get the custom field for.</param>
/// <param name="customFieldName">Name of the custom field to get.</param>
/// <returns><see cref="CustomField" />.</returns>
/// <exception cref="T:System.Net.HttpRequestException">When the call to the remote YouTrack server instance failed.</exception>
public async Task<CustomField> GetProjectCustomField(string projectId, string customFieldName)
{
var client = await _connection.GetAuthenticatedHttpClient();
var response = await client.GetAsync($"rest/admin/project/{projectId}/customfield/{customFieldName}");

response.EnsureSuccessStatusCode();

return JsonConvert.DeserializeObject<CustomField>(await response.Content.ReadAsStringAsync());
}

/// <summary>
/// Remove specified custom field from a project.
/// </summary>
/// <remarks>Uses the REST API <a href="https://www.jetbrains.com/help/youtrack/standalone/DELETE-Project-Custom-Field.html">Delete a project custom field</a>.</remarks>
/// <param name="projectId">Id of the project to delete the custom field for.</param>
/// <param name="customFieldName">Name of the custom field to delete.</param>
/// <exception cref="T:System.ArgumentNullException">When the <paramref name="projectId"/> is null or empty.</exception>
/// <exception cref="T:System.ArgumentNullException">When the <paramref name="customFieldName"/> is null or empty.</exception>
/// <exception cref="T:System.Net.HttpRequestException">When the call to the remote YouTrack server instance failed.</exception>
public async Task DeleteProjectCustomField(string projectId, string customFieldName)
{
if (string.IsNullOrEmpty(projectId))
{
throw new ArgumentNullException(nameof(projectId));
}
if (string.IsNullOrEmpty(customFieldName))
{
throw new ArgumentNullException(nameof(customFieldName));
}

var client = await _connection.GetAuthenticatedHttpClient();
var response = await client.DeleteAsync($"rest/admin/project/{projectId}/customfield/{customFieldName}");

if (response.StatusCode == HttpStatusCode.NotFound)
{
return;
}

response.EnsureSuccessStatusCode();
}

/// <summary>
/// Adds an existing custom field to a specific project.
/// </summary>
/// <remarks>Uses the REST API <a href="https://www.jetbrains.com/help/youtrack/standalone/PUT-Project-Custom-Field.html">Create a project custom field</a>.</remarks>
/// <param name="projectId">Id of the project to add a custom field.</param>
/// <param name="customField"><see cref="CustomField" /> to add to the project.</param>
/// <exception cref="T:System.ArgumentNullException">When the <paramref name="projectId"/> is null or empty.</exception>
/// <exception cref="T:System.ArgumentNullException">When the <paramref name="customField"/> is null or empty.</exception>
/// <exception cref="T:YouTrackErrorException">When the call to the remote YouTrack server instance failed and YouTrack reported an error message.</exception>
/// <exception cref="T:System.Net.HttpRequestException">When the call to the remote YouTrack server instance failed.</exception>
public async Task CreateProjectCustomField(string projectId, CustomField customField)
{
if (string.IsNullOrEmpty(customField?.Name))
{
throw new ArgumentNullException(nameof(customField));
}

string query = string.Empty;
if (!string.IsNullOrEmpty(customField.EmptyText))
{
query = $"?emptyFieldText={WebUtility.UrlEncode(customField.EmptyText)}";
}

var client = await _connection.GetAuthenticatedHttpClient();
var response = await client.PutAsync($"rest/admin/project/{projectId}/customfield/{customField.Name}{query}", new MultipartContent());

response.EnsureSuccessStatusCode();
}

/// <summary>
/// Updates a custom field to a specific project.
/// </summary>
/// <remarks>Uses the REST API <a href="https://www.jetbrains.com/help/youtrack/standalone/POST-Project-Custom-Field.html">Updates a project custom field</a>.</remarks>
/// <param name="projectId">Id of the project.</param>
/// <param name="customField"><see cref="CustomField" /> to update in project.</param>
/// <exception cref="T:System.ArgumentNullException">When the <paramref name="projectId"/> is null or empty.</exception>
/// <exception cref="T:System.ArgumentNullException">When the <paramref name="customField"/> is null or empty.</exception>
/// <exception cref="T:YouTrackErrorException">When the call to the remote YouTrack server instance failed and YouTrack reported an error message.</exception>
/// <exception cref="T:System.Net.HttpRequestException">When the call to the remote YouTrack server instance failed.</exception>
public async Task UpdateProjectCustomField(string projectId, CustomField customField)
{
if (string.IsNullOrEmpty(projectId))
{
throw new ArgumentNullException(nameof(projectId));
}

if (string.IsNullOrEmpty(customField?.Name))
{
throw new ArgumentNullException(nameof(customField));
}

string query = string.Empty;
if (!string.IsNullOrEmpty(customField.EmptyText))
{
query = $"?emptyFieldText={WebUtility.UrlEncode(customField.EmptyText)}";
}

var client = await _connection.GetAuthenticatedHttpClient();
var response = await client.PostAsync($"rest/admin/project/{projectId}/customfield/{customField.Name}{query}", new MultipartContent());

response.EnsureSuccessStatusCode();
}
}
}
2 changes: 1 addition & 1 deletion src/YouTrackSharp/TimeTracking/TimeTrackingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public TimeTrackingService(Connection connection)
/// Get work types for a specific project from the server.
/// </summary>
/// <remarks>Uses the REST API <a href="https://www.jetbrains.com/help/youtrack/standalone/GET-Work-Types-for-a-Project.html">GET Work Types for a Project</a>.</remarks>
/// <param name="projectId">Id of the issue to get work items for.</param>
/// <param name="projectId">Id of the project to get work items for.</param>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks!

/// <returns>An <see cref="T:System.Collections.Generic.IEnumerable`1" /> of <see cref="WorkType" /> for the requested project <paramref name="projectId"/>.</returns>
/// <exception cref="T:System.ArgumentNullException">When the <paramref name="projectId"/> is null or empty.</exception>
/// <exception cref="T:System.Net.HttpRequestException">When the call to the remote YouTrack server instance failed.</exception>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using YouTrackSharp.Projects;
using YouTrackSharp.Tests.Infrastructure;

namespace YouTrackSharp.Tests.Integration.Projects
{
public partial class ProjectCustomFieldsServiceTests
{
public class CreateProjectCustomField
{
[Fact]
public async Task Valid_Connection_Creates_CustomField_For_Project()
{
// Arrange
var connection = Connections.Demo1Password;
var service = connection.ProjectCustomFieldsService();
var customField = new CustomField {Name = "TestField"};
var projectId = "DP1";

// Act
await service.CreateProjectCustomField(projectId, customField);

var created = await service.GetProjectCustomField(projectId, customField.Name);

// Assert
Assert.NotNull(created);

Assert.Equal(customField.Name, created.Name);
Assert.Equal(customField.EmptyText, string.Empty);

// cleanup
await service.DeleteProjectCustomField(projectId, customField.Name);
}

[Fact]
public async Task Valid_Connection_Creates_CustomField_With_EmptyText_For_Project()
{
// Arrange
var connection = Connections.Demo1Password;
var service = connection.ProjectCustomFieldsService();
var customField = new CustomField { Name = "TestField", EmptyText = "empty" };
var projectId = "DP1";

// Act
await service.CreateProjectCustomField(projectId, customField);

var created = await service.GetProjectCustomField(projectId, customField.Name);

// Assert
Assert.NotNull(created);

Assert.Equal(customField.Name, created.Name);
Assert.Equal(customField.EmptyText, created.EmptyText);

// cleanup
await service.DeleteProjectCustomField(projectId, customField.Name);
}

[Fact]
public async Task Invalid_Connection_Throws_UnauthorizedConnectionException()
{
// Arrange
var service = Connections.UnauthorizedConnection.ProjectCustomFieldsService();

// Act & Assert
await Assert.ThrowsAsync<UnauthorizedConnectionException>(
async () => await service.GetProjectCustomField("DP1", "TestField"));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using YouTrackSharp.Tests.Infrastructure;

namespace YouTrackSharp.Tests.Integration.Projects
{
public partial class ProjectCustomFieldsServiceTests
{
public class DeleteProjectCustomField
{
[Fact]
public async Task Valid_Connection_Deletes_CustomField_For_Project()
{
// Arrange
var connection = Connections.Demo1Token;
var service = connection.ProjectCustomFieldsService();

// Act & Assert
var acted = false;
await service.DeleteProjectCustomField("DP1", " TestField");
acted = true;

Assert.True(acted);
}

[Fact]
public async Task Invalid_Connection_Throws_UnauthorizedConnectionException()
{
// Arrange
var service = Connections.UnauthorizedConnection.ProjectCustomFieldsService();

// Act & Assert
await Assert.ThrowsAsync<UnauthorizedConnectionException>(
async () => await service.DeleteProjectCustomField("DP1", "TestField"));
}
}
}
}
Loading