initial commit
This commit is contained in:
206
BlueMine/Redmine/RedmineManager.cs
Normal file
206
BlueMine/Redmine/RedmineManager.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Net.Http;
|
||||
using static BlueMine.Redmine.RedmineDto;
|
||||
|
||||
namespace BlueMine.Redmine
|
||||
{
|
||||
public class RedmineManager : IRedmineManager
|
||||
{
|
||||
private readonly RedmineConnect _redmineConnect;
|
||||
private readonly AsyncLock _lock = new();
|
||||
private readonly RedmineCache<SimpleProject> _projectCache;
|
||||
private readonly RedmineCache<SimpleIssue> _issueCache;
|
||||
private readonly ILogger<RedmineManager> _logger;
|
||||
|
||||
public RedmineManager(HttpClient httpClient, ILoggerFactory loggerFactory, RedmineConfig config)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(httpClient);
|
||||
ArgumentNullException.ThrowIfNull(loggerFactory);
|
||||
ArgumentNullException.ThrowIfNull(config);
|
||||
|
||||
_logger = loggerFactory.CreateLogger<RedmineManager>();
|
||||
_logger.LogDebug("Initializing RedmineManager with URL: {Url}", config.RedmineUrl);
|
||||
_redmineConnect = new RedmineConnect(httpClient, loggerFactory.CreateLogger<RedmineConnect>(), config);
|
||||
_projectCache = new(config.ProjectCacheDuration, loggerFactory.CreateLogger<RedmineCache<SimpleProject>>());
|
||||
_issueCache = new(config.IssueCacheDuration, loggerFactory.CreateLogger<RedmineCache<SimpleIssue>>());
|
||||
_logger.LogDebug("RedmineManager initialized");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the Redmine instance is available by verifying the API key.
|
||||
/// </summary>
|
||||
/// <returns>True if available, false otherwise.</returns>
|
||||
public async Task<bool> IsRedmineAvailable()
|
||||
{
|
||||
_logger.LogDebug("Checking if Redmine is available");
|
||||
try
|
||||
{
|
||||
using (await _lock.LockAsync())
|
||||
{
|
||||
await _redmineConnect.VerifyApiKey();
|
||||
}
|
||||
_logger.LogDebug("Redmine is available");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug("Redmine not available: {Message}", ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Logs time for a specific issue.
|
||||
/// </summary>
|
||||
/// <param name="issueId">The issue ID.</param>
|
||||
/// <param name="hours">Hours to log.</param>
|
||||
/// <param name="comments">Comments for the time entry.</param>
|
||||
/// <param name="date">Date of the time entry.</param>
|
||||
/// <param name="activityId">Optional activity ID.</param>
|
||||
public async Task LogTimeAsync(int issueId, double hours, string comments, DateTime date, int? activityId = null)
|
||||
{
|
||||
_logger.LogDebug("Logging {Hours} hours for issue {IssueId} on {Date}", hours, issueId, date.ToShortDateString());
|
||||
using (await _lock.LockAsync())
|
||||
{
|
||||
await _redmineConnect.LogTimeAsync(issueId, hours, comments, date, activityId);
|
||||
}
|
||||
_logger.LogDebug("Time logged successfully");
|
||||
}
|
||||
/// <summary>
|
||||
/// Closes the specified issue.
|
||||
/// </summary>
|
||||
/// <param name="issueId">The issue ID to close.</param>
|
||||
public async Task CloseIssueAsync(int issueId)
|
||||
{
|
||||
_logger.LogDebug("Closing issue {IssueId}", issueId);
|
||||
using (await _lock.LockAsync())
|
||||
{
|
||||
await _redmineConnect.CloseIssueAsync(issueId);
|
||||
}
|
||||
_logger.LogDebug("Issue {IssueId} closed", issueId);
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new issue in the specified project.
|
||||
/// </summary>
|
||||
/// <param name="projectId">The project ID.</param>
|
||||
/// <param name="trackerId">The tracker ID.</param>
|
||||
/// <param name="subject">Issue subject.</param>
|
||||
/// <param name="description">Optional description.</param>
|
||||
/// <param name="estimatedHours">Optional estimated hours.</param>
|
||||
/// <param name="priorityId">Optional priority ID.</param>
|
||||
/// <param name="parentIssueId">Optional parent issue ID.</param>
|
||||
/// <returns>The created issue ID.</returns>
|
||||
public async Task<int> CreateIssueAsync(string projectId, int trackerId, string subject, string? description = null,
|
||||
double? estimatedHours = null, int? priorityId = 9, int? parentIssueId = null)
|
||||
{
|
||||
_logger.LogDebug("Creating issue in project {ProjectId} with subject '{Subject}'", projectId, subject);
|
||||
using (await _lock.LockAsync())
|
||||
{
|
||||
var issueId = await _redmineConnect.CreateIssueAsync(projectId, trackerId, subject, description,
|
||||
estimatedHours, priorityId, parentIssueId);
|
||||
_logger.LogDebug("Issue created with ID {IssueId}", issueId);
|
||||
return issueId;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Retrieves the list of projects, using cache if valid.
|
||||
/// </summary>
|
||||
/// <param name="limit">Maximum number of projects to fetch per request.</param>
|
||||
/// <param name="progress">Optional progress reporter.</param>
|
||||
/// <returns>List of simple projects.</returns>
|
||||
public async Task<List<SimpleProject>> GetProjectsAsync(int limit = 100, IProgress<(int, int)>? progress = null)
|
||||
{
|
||||
_logger.LogDebug("Getting projects");
|
||||
using (await _lock.LockAsync())
|
||||
{
|
||||
List<SimpleProject> projects = [];
|
||||
|
||||
if(!_projectCache.IsCacheValid())
|
||||
{
|
||||
_logger.LogDebug("Cache invalid, refreshing");
|
||||
_projectCache.RefreshCache([..(await _redmineConnect.GetProjectsAsync(limit, progress))]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("Using cached projects");
|
||||
}
|
||||
|
||||
projects = _projectCache.GetItems();
|
||||
_logger.LogDebug("Retrieved {Count} projects", projects.Count);
|
||||
return projects;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Retrieves trackers for the specified project.
|
||||
/// </summary>
|
||||
/// <param name="projectId">The project ID.</param>
|
||||
/// <param name="token">Optional cancellation token.</param>
|
||||
/// <returns>List of simple trackers.</returns>
|
||||
public async Task<List<SimpleTracker>> GetTrackersAsync(string projectId, CancellationToken? token = null)
|
||||
{
|
||||
_logger.LogDebug("Getting trackers for project {ProjectId}", projectId);
|
||||
try
|
||||
{
|
||||
using (await _lock.LockAsync())
|
||||
{
|
||||
var trackers = await _redmineConnect.GetTrackersAsync(projectId, token);
|
||||
_logger.LogDebug("Retrieved {Count} trackers", trackers.Count);
|
||||
return trackers;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogDebug("GetTrackersAsync cancelled");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Retrieves current issues with spent time.
|
||||
/// </summary>
|
||||
/// <param name="progress">Optional progress reporter.</param>
|
||||
/// <returns>List of issue items.</returns>
|
||||
public async Task<List<IssueItem>> GetCurrentIssuesAsync(IProgress<(int, int)>? progress = null)
|
||||
{
|
||||
_logger.LogDebug("Getting current issues");
|
||||
using (await _lock.LockAsync())
|
||||
{
|
||||
List<SimpleIssue> simpleIssues;
|
||||
if (!_issueCache.IsCacheValid())
|
||||
{
|
||||
_logger.LogDebug("Issue cache invalid, refreshing");
|
||||
simpleIssues = [.. (await _redmineConnect.GetMyIssuesAsync())];
|
||||
_issueCache.RefreshCache(simpleIssues);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("Using cached issues");
|
||||
simpleIssues = _issueCache.GetItems();
|
||||
}
|
||||
var issues = await _redmineConnect.GetSpentTimeForIssuesAsync(simpleIssues, progress);
|
||||
_logger.LogDebug("Retrieved {Count} issues", issues.Count);
|
||||
return issues;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Retrieves logged hours for the specified date range.
|
||||
/// </summary>
|
||||
/// <param name="startDate">Start date.</param>
|
||||
/// <param name="endDate">End date.</param>
|
||||
/// <returns>Total logged hours.</returns>
|
||||
public async Task<double> GetLoggedHoursAsync(DateTime? startDate = null, DateTime? endDate = null)
|
||||
{
|
||||
var start = DateTime.Today;
|
||||
var end = DateTime.Today;
|
||||
if (startDate.HasValue)
|
||||
start = startDate.Value;
|
||||
if(endDate.HasValue)
|
||||
end = endDate.Value;
|
||||
_logger.LogDebug("Getting logged hours from {Start} to {End}", start.ToShortDateString(), end.ToShortDateString());
|
||||
using (await _lock.LockAsync())
|
||||
{
|
||||
var hours = await _redmineConnect.GetTodaysHoursAsync(start, end);
|
||||
_logger.LogDebug("Retrieved {Hours} hours", hours);
|
||||
return hours;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user