add hour window
This commit is contained in:
@@ -5,18 +5,6 @@ namespace Blueberry.Redmine.Dto
|
||||
{
|
||||
public class UserInfo
|
||||
{
|
||||
public class CustomField
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("value")]
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
public class Root
|
||||
{
|
||||
[JsonPropertyName("user")]
|
||||
@@ -39,6 +27,7 @@ namespace Blueberry.Redmine.Dto
|
||||
|
||||
[JsonPropertyName("lastname")]
|
||||
public string Lastname { get; set; }
|
||||
public string FullName => Lastname + " " + Firstname;
|
||||
|
||||
[JsonPropertyName("mail")]
|
||||
public string Mail { get; set; }
|
||||
@@ -50,22 +39,7 @@ namespace Blueberry.Redmine.Dto
|
||||
public DateTime UpdatedOn { get; set; }
|
||||
|
||||
[JsonPropertyName("last_login_on")]
|
||||
public DateTime LastLoginOn { get; set; }
|
||||
|
||||
[JsonPropertyName("passwd_changed_on")]
|
||||
public DateTime PasswdChangedOn { get; set; }
|
||||
|
||||
[JsonPropertyName("twofa_scheme")]
|
||||
public object TwofaScheme { get; set; }
|
||||
|
||||
[JsonPropertyName("api_key")]
|
||||
public string ApiKey { get; set; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public int Status { get; set; }
|
||||
|
||||
[JsonPropertyName("custom_fields")]
|
||||
public List<CustomField> CustomFields { get; set; }
|
||||
public DateTime? LastLoginOn { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
24
Blueberry.Redmine/Dto/UserList.cs
Normal file
24
Blueberry.Redmine/Dto/UserList.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Blueberry.Redmine.Dto
|
||||
{
|
||||
public class UserList
|
||||
{
|
||||
public class Root : IResponseList
|
||||
{
|
||||
[JsonPropertyName("users")]
|
||||
public List<UserInfo.User> Users { get; set; }
|
||||
|
||||
[JsonPropertyName("total_count")]
|
||||
public int TotalCount { get; set; }
|
||||
|
||||
[JsonPropertyName("offset")]
|
||||
public int Offset { get; set; }
|
||||
|
||||
[JsonPropertyName("limit")]
|
||||
public int Limit { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
@@ -1,10 +1,8 @@
|
||||
using Blueberry.Redmine.Dto;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using static Blueberry.Redmine.Dto.UserTime;
|
||||
|
||||
namespace Blueberry.Redmine
|
||||
{
|
||||
@@ -88,10 +86,14 @@ namespace Blueberry.Redmine
|
||||
|
||||
while (true)
|
||||
{
|
||||
var path = $"{endpoint}&limit={limit}&offset={offset}";
|
||||
var path = "";
|
||||
if(endpoint.Contains('?'))
|
||||
path = $"{endpoint}&limit={limit}&offset={offset}";
|
||||
else
|
||||
path = $"{endpoint}?limit={limit}&offset={offset}";
|
||||
|
||||
var responseList = await SendRequestAsync<TResponse>(HttpMethod.Get, path, token: token)
|
||||
?? throw new NullReferenceException();
|
||||
?? throw new NullReferenceException();
|
||||
|
||||
returnList.AddRange(itemParser(responseList));
|
||||
|
||||
@@ -139,7 +141,7 @@ namespace Blueberry.Redmine
|
||||
return fields.IssuePriorities;
|
||||
}
|
||||
|
||||
public async Task<List<IssueList.Issue>> GetOpenIssuesByAssignee(int userId, int limit = PAGING_LIMIT, IProgress<(int, int)>? progress = null, CancellationToken ? token = null)
|
||||
public async Task<List<IssueList.Issue>> GetOpenIssuesByAssigneeAsync(int userId, int limit = PAGING_LIMIT, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
{
|
||||
var path = $"issues.json?assigned_to_id={userId}&status_id=open";
|
||||
|
||||
@@ -149,7 +151,138 @@ namespace Blueberry.Redmine
|
||||
return items;
|
||||
}
|
||||
|
||||
public async Task<List<ProjectList.Project>> GetProjects(int limit = PAGING_LIMIT, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
public async Task<List<IssueList.Issue>> GetIssuesAsync(
|
||||
int? userId = null,
|
||||
string? projectId = null,
|
||||
int? statusId = null,
|
||||
bool? isOpen = null,
|
||||
DateTime? createdOn = null,
|
||||
DateTime? updatedOn = null,
|
||||
int limit = PAGING_LIMIT,
|
||||
IProgress<(int, int)>? progress = null,
|
||||
CancellationToken? token = null)
|
||||
{
|
||||
// Start with the base endpoint
|
||||
// We use a List to build parameters cleanly to avoid formatting errors
|
||||
var queryParams = new List<string>();
|
||||
|
||||
// 1. Handle User ID
|
||||
if (userId != null)
|
||||
queryParams.Add($"assigned_to_id={userId}");
|
||||
|
||||
// 2. Handle Project ID
|
||||
if (!string.IsNullOrEmpty(projectId))
|
||||
queryParams.Add($"project_id={projectId}");
|
||||
|
||||
// 3. Handle Status (Prioritize explicit ID, then isOpen flag, then default to open)
|
||||
if (statusId != null)
|
||||
{
|
||||
queryParams.Add($"status_id={statusId}");
|
||||
}
|
||||
else if (isOpen != null)
|
||||
{
|
||||
queryParams.Add($"status_id={(isOpen.Value ? "open" : "closed")}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default behavior if neither is specified (preserves your original logic)
|
||||
queryParams.Add("status_id=open");
|
||||
}
|
||||
|
||||
// 4. Handle Dates (Using >= operator for "Since")
|
||||
if (createdOn != null)
|
||||
{
|
||||
// %3E%3D is ">="
|
||||
queryParams.Add($"created_on=%3E%3D{createdOn.Value:yyyy-MM-ddTHH:mm:ssZ}");
|
||||
}
|
||||
|
||||
if (updatedOn != null)
|
||||
{
|
||||
queryParams.Add($"updated_on=%3E%3D{updatedOn.Value:yyyy-MM-ddTHH:mm:ssZ}");
|
||||
}
|
||||
|
||||
// Join the path with the query string
|
||||
var queryString = string.Join("&", queryParams);
|
||||
var path = $"issues.json?{queryString}";
|
||||
|
||||
return await SendRequestWithPagingAsync<IssueList.Root, IssueList.Issue>(
|
||||
HttpMethod.Get, path, limit, (x) => x.Issues, progress, token: token);
|
||||
}
|
||||
|
||||
public async Task<List<IssueList.Issue>> GetIssuesAsync(
|
||||
int? userId = null,
|
||||
string? projectId = null,
|
||||
int? statusId = null,
|
||||
bool? isOpen = null,
|
||||
// Changed single dates to From/To pairs
|
||||
DateTime? createdFrom = null,
|
||||
DateTime? createdTo = null,
|
||||
DateTime? updatedFrom = null,
|
||||
DateTime? updatedTo = null,
|
||||
int limit = PAGING_LIMIT,
|
||||
IProgress<(int, int)>? progress = null,
|
||||
CancellationToken? token = null)
|
||||
{
|
||||
var queryParams = new List<string>();
|
||||
|
||||
// 1. Basic Filters
|
||||
if (userId != null) queryParams.Add($"assigned_to_id={userId}");
|
||||
if (!string.IsNullOrEmpty(projectId)) queryParams.Add($"project_id={projectId}");
|
||||
|
||||
// 2. Status Logic
|
||||
if (statusId != null)
|
||||
{
|
||||
queryParams.Add($"status_id={statusId}");
|
||||
}
|
||||
else if (isOpen != null)
|
||||
{
|
||||
queryParams.Add($"status_id={(isOpen.Value ? "open" : "closed")}");
|
||||
}
|
||||
else
|
||||
{
|
||||
queryParams.Add("status_id=open");
|
||||
}
|
||||
|
||||
// 3. Date Filter Logic (Helper function used below)
|
||||
string? createdFilter = BuildDateFilter("created_on", createdFrom, createdTo);
|
||||
if (createdFilter != null) queryParams.Add(createdFilter);
|
||||
|
||||
string? updatedFilter = BuildDateFilter("updated_on", updatedFrom, updatedTo);
|
||||
if (updatedFilter != null) queryParams.Add(updatedFilter);
|
||||
|
||||
// 4. Construct URL
|
||||
var queryString = string.Join("&", queryParams);
|
||||
var path = $"issues.json?{queryString}";
|
||||
|
||||
return await SendRequestWithPagingAsync<IssueList.Root, IssueList.Issue>(
|
||||
HttpMethod.Get, path, limit, (x) => x.Issues, progress, token: token);
|
||||
}
|
||||
|
||||
// Helper method to determine the correct Redmine operator
|
||||
private string? BuildDateFilter(string paramName, DateTime? from, DateTime? to)
|
||||
{
|
||||
string format = "yyyy-MM-ddTHH:mm:ssZ"; // ISO 8601
|
||||
|
||||
if (from.HasValue && to.HasValue)
|
||||
{
|
||||
// Range: "><START|END" (URL encoded as %3E%3C)
|
||||
return $"{paramName}=%3E%3C{from.Value.ToString(format)}|{to.Value.ToString(format)}";
|
||||
}
|
||||
else if (from.HasValue)
|
||||
{
|
||||
// After: ">=DATE" (URL encoded as %3E%3D)
|
||||
return $"{paramName}=%3E%3D{from.Value.ToString(format)}";
|
||||
}
|
||||
else if (to.HasValue)
|
||||
{
|
||||
// Before: "<=DATE" (URL encoded as %3C%3D)
|
||||
return $"{paramName}=%3C%3D{to.Value.ToString(format)}";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<List<ProjectList.Project>> GetProjectsAsync(int limit = PAGING_LIMIT, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
{
|
||||
var path = $"projects.json";
|
||||
|
||||
@@ -159,7 +292,7 @@ namespace Blueberry.Redmine
|
||||
return items;
|
||||
}
|
||||
|
||||
public async Task<List<ProjectTrackers.Tracker>> GetTrackersForProject(string projectId, CancellationToken? token = null)
|
||||
public async Task<List<ProjectTrackers.Tracker>> GetTrackersForProjectAsync(string projectId, CancellationToken? token = null)
|
||||
{
|
||||
var path = $"projects/{projectId}.json?include=trackers";
|
||||
|
||||
@@ -169,7 +302,7 @@ namespace Blueberry.Redmine
|
||||
return trackers.Project.Trackers;
|
||||
}
|
||||
|
||||
public async Task<double> GetTotalTimeForUser(int userId, DateTime start, DateTime end, int limit = PAGING_LIMIT, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
public async Task<double> GetTotalTimeForUserAsync(int userId, DateTime start, DateTime end, int limit = PAGING_LIMIT, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
{
|
||||
var sText = start.ToString("yyyy-MM-dd");
|
||||
var eText = end.ToString("yyyy-MM-dd");
|
||||
@@ -177,14 +310,24 @@ namespace Blueberry.Redmine
|
||||
var path = $"time_entries.json?from={sText}&to={eText}&user_id={userId}";
|
||||
|
||||
|
||||
var timedata = await SendRequestWithPagingAsync<UserTime.Root, UserTime.TimeEntry>(HttpMethod.Get, path, limit, (x)=> x.TimeEntries, progress, token: token);
|
||||
var timedata = await SendRequestWithPagingAsync<UserTime.Root, UserTime.TimeEntry>(HttpMethod.Get, path, limit, (x) => x.TimeEntries, progress, token: token);
|
||||
|
||||
var hours = timedata.Sum(x => x.Hours);
|
||||
|
||||
return hours;
|
||||
}
|
||||
public async Task<List<UserTime.TimeEntry>> GetTimeForUserAsync(int userId, DateTime start, DateTime end, int limit = PAGING_LIMIT, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
{
|
||||
var sText = start.ToString("yyyy-MM-dd");
|
||||
var eText = end.ToString("yyyy-MM-dd");
|
||||
|
||||
public async Task<DetailedIssue.Issue> GetIssue(int issueId, CancellationToken? token = null)
|
||||
var path = $"time_entries.json?from={sText}&to={eText}&user_id={userId}";
|
||||
|
||||
|
||||
return await SendRequestWithPagingAsync<UserTime.Root, UserTime.TimeEntry>(HttpMethod.Get, path, limit, (x) => x.TimeEntries, progress, token: token);
|
||||
}
|
||||
|
||||
public async Task<DetailedIssue.Issue> GetIssueAsync(int issueId, CancellationToken? token = null)
|
||||
{
|
||||
var path = $"issues/{issueId}.json?include=journals";
|
||||
|
||||
@@ -194,7 +337,7 @@ namespace Blueberry.Redmine
|
||||
return issue.Issue;
|
||||
}
|
||||
|
||||
public async Task<IssueList.Issue> GetSimpleIssue(int issueId, CancellationToken? token = null)
|
||||
public async Task<IssueList.Issue> GetSimpleIssueAsync(int issueId, CancellationToken? token = null)
|
||||
{
|
||||
var path = $"issues/{issueId}.json?include=journals";
|
||||
|
||||
@@ -217,7 +360,14 @@ namespace Blueberry.Redmine
|
||||
return user.User;
|
||||
}
|
||||
|
||||
public async Task SetIssueStatus(int issueId, int statusId, CancellationToken? token = null)
|
||||
public async Task<List<UserInfo.User>> GetUsersAsync(int limit = PAGING_LIMIT, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
{
|
||||
var path = "users.json";
|
||||
|
||||
return await SendRequestWithPagingAsync<UserList.Root, UserInfo.User>(HttpMethod.Get, path, limit, (x) => x.Users, progress, token);
|
||||
}
|
||||
|
||||
public async Task SetIssueStatusAsync(int issueId, int statusId, CancellationToken? token = null)
|
||||
{
|
||||
var path = $"issues/{issueId}.json";
|
||||
|
||||
@@ -232,14 +382,39 @@ namespace Blueberry.Redmine
|
||||
await SendRequestAsync<object>(HttpMethod.Put, path, payload, token: token);
|
||||
}
|
||||
|
||||
public async Task<List<TimeOnIssue.TimeEntry>> GetTimeOnIssue(int issueId, int limit = 25, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
public async Task AddCommentToIssueAsync(int issueId, string comment, bool isPrivate, CancellationToken? token = null)
|
||||
{
|
||||
var path = $"issues/{issueId}.json";
|
||||
|
||||
var payload = new
|
||||
{
|
||||
issue = new
|
||||
{
|
||||
notes = comment
|
||||
}
|
||||
};
|
||||
var privatePayload = new
|
||||
{
|
||||
issue = new
|
||||
{
|
||||
private_notes = comment
|
||||
}
|
||||
};
|
||||
|
||||
if(isPrivate)
|
||||
await SendRequestAsync<object>(HttpMethod.Put, path, privatePayload, token: token);
|
||||
else
|
||||
await SendRequestAsync<object>(HttpMethod.Put, path, payload, token: token);
|
||||
}
|
||||
|
||||
public async Task<List<TimeOnIssue.TimeEntry>> GetTimeOnIssueAsync(int issueId, int limit = 25, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
{
|
||||
var path = $"time_entries.json?issue_id={issueId}";
|
||||
var times = await SendRequestWithPagingAsync<TimeOnIssue.Root, TimeOnIssue.TimeEntry>(HttpMethod.Get, path, limit, (x)=>x.TimeEntries, progress, token: token);
|
||||
return times;
|
||||
}
|
||||
|
||||
public async Task<int> CreateNewIssue(int projectId, int trackerId, string subject, string description,
|
||||
public async Task<int> CreateNewIssueAsync(int projectId, int trackerId, string subject, string description,
|
||||
double estimatedHours, int priorityId, int? assigneeId = null, int? parentIssueId = null, CancellationToken? token = null)
|
||||
{
|
||||
var path = "issues.json";
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Blueberry.Redmine
|
||||
{
|
||||
public class RedmineManager
|
||||
{
|
||||
private readonly TimeSpan DEFAULT_CACHE_DURATION = TimeSpan.FromHours(1);
|
||||
private readonly TimeSpan DEFAULT_CACHE_DURATION = TimeSpan.FromHours(3);
|
||||
private readonly RedmineConfig _config;
|
||||
private readonly ILogger _logger;
|
||||
private readonly RedmineApiClient _apiClient;
|
||||
@@ -13,6 +13,7 @@ namespace Blueberry.Redmine
|
||||
private readonly RedmineCache<PriorityList.IssuePriority> _priorityCache;
|
||||
private readonly RedmineCache<CustomFieldList.CustomField> _customFieldCache;
|
||||
private readonly RedmineCache<ProjectList.Project> _projectCache;
|
||||
private readonly RedmineCache<UserInfo.User> _userCache;
|
||||
|
||||
public RedmineManager(RedmineConfig config, HttpClient client, ILoggerFactory loggerFactory)
|
||||
{
|
||||
@@ -31,6 +32,9 @@ namespace Blueberry.Redmine
|
||||
_projectCache = new RedmineCache<ProjectList.Project>(
|
||||
DEFAULT_CACHE_DURATION, loggerFactory.CreateLogger<RedmineCache<ProjectList.Project>>(), cacheFilePath: $"{_config.CacheFilePath}Projects.json");
|
||||
|
||||
_userCache = new RedmineCache<UserInfo.User>(
|
||||
DEFAULT_CACHE_DURATION, loggerFactory.CreateLogger<RedmineCache<UserInfo.User>>(), cacheFilePath: $"{_config.CacheFilePath}Users.json");
|
||||
|
||||
_logger = loggerFactory.CreateLogger<RedmineManager>();
|
||||
_logger.LogDebug("Initialized caches");
|
||||
}
|
||||
@@ -87,11 +91,22 @@ namespace Blueberry.Redmine
|
||||
{
|
||||
return await _projectCache.GetItemsAsync();
|
||||
}
|
||||
var projects = await _apiClient.GetProjects(limit, progress, token);
|
||||
var projects = await _apiClient.GetProjectsAsync(limit, progress, token);
|
||||
await _projectCache.RefreshCacheAsync(projects);
|
||||
return projects;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<UserInfo.User>> GetUsersAsync(int limit = 50, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
{
|
||||
if (_userCache.IsCacheValid())
|
||||
{
|
||||
return await _userCache.GetItemsAsync();
|
||||
}
|
||||
var users = await _apiClient.GetUsersAsync(limit, progress, token);
|
||||
await _userCache.RefreshCacheAsync(users);
|
||||
return users;
|
||||
}
|
||||
|
||||
public async Task<UserInfo.User> GetCurrentUserAsync(CancellationToken? token = null)
|
||||
{
|
||||
var user = await _apiClient.GetUserAsync(token: token);
|
||||
@@ -104,42 +119,74 @@ namespace Blueberry.Redmine
|
||||
return user;
|
||||
}
|
||||
|
||||
public async Task<List<IssueList.Issue>> GetCurrentUserIssuesAsync(int limit = 50, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
public async Task<List<IssueList.Issue>> GetCurrentUserOpenIssuesAsync(int limit = 50, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
{
|
||||
var user = await GetCurrentUserAsync(token);
|
||||
return await _apiClient.GetOpenIssuesByAssignee(user.Id, limit, progress, token);
|
||||
return await _apiClient.GetOpenIssuesByAssigneeAsync(user.Id, limit, progress, token);
|
||||
}
|
||||
|
||||
public async Task<List<IssueList.Issue>> GetUserOpenIssuesAsync(int userId, int limit = 50, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
{
|
||||
return await _apiClient.GetOpenIssuesByAssigneeAsync(userId, limit, progress, token);
|
||||
}
|
||||
|
||||
public async Task<List<IssueList.Issue>> GetIssuesAsync(
|
||||
int? userId = null,
|
||||
string? projectId = null,
|
||||
int? statusId = null,
|
||||
bool? isOpen = null,
|
||||
// Changed single dates to From/To pairs
|
||||
DateTime? createdFrom = null,
|
||||
DateTime? createdTo = null,
|
||||
DateTime? updatedFrom = null,
|
||||
DateTime? updatedTo = null,
|
||||
int limit = 50,
|
||||
IProgress<(int, int)>? progress = null,
|
||||
CancellationToken? token = null)
|
||||
{
|
||||
return await _apiClient.GetIssuesAsync(userId, projectId, statusId, isOpen, createdFrom, createdTo, updatedFrom, updatedTo, limit, progress, token);
|
||||
}
|
||||
|
||||
public async Task AddComment(int issueId, string comment, bool isPrivate = false, CancellationToken? token = null)
|
||||
{
|
||||
await _apiClient.AddCommentToIssueAsync(issueId, comment, isPrivate, token);
|
||||
}
|
||||
|
||||
public async Task<double> GetCurrentUserTimeAsync(DateTime start, DateTime end, int limit = 50, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
{
|
||||
var user = await GetCurrentUserAsync(token);
|
||||
return await _apiClient.GetTotalTimeForUser(user.Id, start, end, limit, progress, token);
|
||||
return await _apiClient.GetTotalTimeForUserAsync(user.Id, start, end, limit, progress, token);
|
||||
}
|
||||
|
||||
public async Task<double> GetUserTimeAsync(int userId, DateTime start, DateTime end, int limit = 50, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
{
|
||||
return await _apiClient.GetTotalTimeForUserAsync(userId, start, end, limit, progress, token);
|
||||
}
|
||||
|
||||
public async Task<DetailedIssue.Issue> GetIssueAsync(int issueId, CancellationToken? token = null)
|
||||
{
|
||||
return await _apiClient.GetIssue(issueId, token);
|
||||
return await _apiClient.GetIssueAsync(issueId, token);
|
||||
}
|
||||
|
||||
public async Task<IssueList.Issue> GetSimpleIssueAsync(int issueId, CancellationToken? token = null)
|
||||
{
|
||||
return await _apiClient.GetSimpleIssue(issueId, token);
|
||||
return await _apiClient.GetSimpleIssueAsync(issueId, token);
|
||||
}
|
||||
|
||||
public async Task<List<ProjectTrackers.Tracker>> GetProjectTrackersAsync(int projectId, CancellationToken? token = null)
|
||||
{
|
||||
return await _apiClient.GetTrackersForProject(projectId.ToString(), token);
|
||||
return await _apiClient.GetTrackersForProjectAsync(projectId.ToString(), token);
|
||||
}
|
||||
|
||||
public async Task<List<TimeOnIssue.TimeEntry>> GetTimeOnIssue(int issueId, int limit = 25, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
{
|
||||
var result = await _apiClient.GetTimeOnIssue(issueId, limit, progress, token);
|
||||
var result = await _apiClient.GetTimeOnIssueAsync(issueId, limit, progress, token);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task SetIssueStatusAsync(int issueId, int statusId, CancellationToken? token = null)
|
||||
{
|
||||
await _apiClient.SetIssueStatus(issueId, statusId, token);
|
||||
await _apiClient.SetIssueStatusAsync(issueId, statusId, token);
|
||||
}
|
||||
|
||||
public async Task LogTimeAsync(int issueId, double hours, string comments, DateTime? date = null, int? activityId = null, CancellationToken? token = null)
|
||||
@@ -150,7 +197,7 @@ namespace Blueberry.Redmine
|
||||
public async Task<int> CreateIssueAsync(int projectId, int trackerId, string subject, string description,
|
||||
double estimatedHours, int priorityId, int? assigneeId = null, int? parentIssueId = null, CancellationToken? token = null)
|
||||
{
|
||||
return await _apiClient.CreateNewIssue(projectId, trackerId, subject, description, estimatedHours, priorityId, assigneeId, parentIssueId, token);
|
||||
return await _apiClient.CreateNewIssueAsync(projectId, trackerId, subject, description, estimatedHours, priorityId, assigneeId, parentIssueId, token);
|
||||
}
|
||||
|
||||
public async Task<double> GetCurrentUserTimeTodayAsync(int limit = 50, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
@@ -169,5 +216,10 @@ namespace Blueberry.Redmine
|
||||
var end = start.AddMonths(1).AddDays(-1);
|
||||
return await GetCurrentUserTimeAsync(start, end, limit, progress, token);
|
||||
}
|
||||
|
||||
public async Task<List<UserTime.TimeEntry>> GetTimeForUserAsync(int userId, DateTime start, DateTime end, int limit = 50, IProgress<(int, int)>? progress = null, CancellationToken? token = null)
|
||||
{
|
||||
return await _apiClient.GetTimeForUserAsync(userId, start, end, limit, progress, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user