Merge branch 'dev' of http://gitlab.dvgd.oao.rzd/devptk/parr/parr_api into dev
This commit is contained in:
@@ -212,11 +212,21 @@ namespace PARR.API.Contracts.V1
|
||||
public const string GetPeriodStatistics = BaseStat + "/robot-tasks/{robot}/period/";
|
||||
}
|
||||
|
||||
public static class StatRobotTaskDetails
|
||||
{
|
||||
public const string Details = BaseStat + "/robot-tasks/details/{robot}/{task}";
|
||||
}
|
||||
|
||||
public static class StatRobotStatus
|
||||
{
|
||||
public const string Get = BaseStat + "/robot-statuses/";
|
||||
}
|
||||
|
||||
public static class StatRobotStatusDetails
|
||||
{
|
||||
public const string Details = BaseStat + "/robot-statuses/details/{robot}/{status}";
|
||||
}
|
||||
|
||||
public static class StatRobotHistory
|
||||
{
|
||||
public const string Get = BaseStat + "/robot-histories/";
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PARR.API.Contracts.V1;
|
||||
using PARR.API.Controllers.V1.Base;
|
||||
using PARR.Domain.Common.Roles;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.API.Controllers.V1.Statistics
|
||||
{
|
||||
/// <summary>
|
||||
/// Детальная статистика по статусам заданий роботам
|
||||
/// </summary>
|
||||
[Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)]
|
||||
public class StatRobotStatusDetailsController : BaseApiController
|
||||
{
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public StatRobotStatusDetailsController(
|
||||
IMapper mapper
|
||||
)
|
||||
{
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Список групп работ по статусам заданий роботам
|
||||
/// </summary>
|
||||
/// <param name="robot"></param>
|
||||
/// <param name="status"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet(ApiRoutes.StatRobotStatusDetails.Details)]
|
||||
public async Task<IActionResult> Details([FromRoute] RobotsEnum robot, [FromRoute] RobotStatusEnum status)
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PARR.API.Contracts.V1;
|
||||
using PARR.API.Controllers.V1.Base;
|
||||
using PARR.Core.Services.RobotTaskDetailsServices.Interfaces;
|
||||
using PARR.Domain.Common.Roles;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.API.Controllers.V1.Statistics
|
||||
{
|
||||
/// <summary>
|
||||
/// Детальная статистика по заданиям роботам
|
||||
/// </summary>
|
||||
[Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)]
|
||||
public class StatRobotTaskDetailsController : BaseApiController
|
||||
{
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IRobotTaskDetailsService _robotTaskDetailsService;
|
||||
|
||||
public StatRobotTaskDetailsController(
|
||||
IMapper mapper,
|
||||
IRobotTaskDetailsService robotTaskDetailsService
|
||||
)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_robotTaskDetailsService = robotTaskDetailsService;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Список групп работ по заданиям роботам
|
||||
/// </summary>
|
||||
/// <param name="robot"></param>
|
||||
/// <param name="task"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet(ApiRoutes.StatRobotTaskDetails.Details)]
|
||||
public async Task<IActionResult> Details([FromRoute] RobotsEnum robot, [FromRoute] TaskStatusEnum task)
|
||||
{
|
||||
var details = await _robotTaskDetailsService.GetDetailsAsync(robot, task);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PARR.API.Contracts.V1;
|
||||
using PARR.API.Contracts.V1.Requests.BaseRequests;
|
||||
@@ -8,10 +9,12 @@ using PARR.API.Controllers.V1.Base;
|
||||
using PARR.API.Helpers;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Services.NextRunServices;
|
||||
using PARR.Domain.Common.Roles;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.API.Controllers.V1.Statistics
|
||||
{
|
||||
[Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)]
|
||||
public class StatTemplateAutoControlController : BaseApiController
|
||||
{
|
||||
private readonly ITemplateRepository templateService;
|
||||
|
||||
@@ -12,6 +12,8 @@ using PARR.Core.Services.RobotMetrics;
|
||||
using PARR.Core.Services.RobotSnapshotServices;
|
||||
using PARR.Core.Services.RobotTask.Implementations;
|
||||
using PARR.Core.Services.RobotTask.Interfaces;
|
||||
using PARR.Core.Services.RobotTaskDetailsServices.Implementations;
|
||||
using PARR.Core.Services.RobotTaskDetailsServices.Interfaces;
|
||||
using PARR.Core.Services.RobotTaskRobotStatus.Implemetations;
|
||||
using PARR.Core.Services.RobotTaskRobotStatus.Interfaces;
|
||||
using PARR.Core.Services.Shortcodes;
|
||||
@@ -114,6 +116,7 @@ namespace PARR.Core
|
||||
services.AddScoped<IRobotTaskService, RobotTaskService>();
|
||||
services.AddScoped<IRobotSnapshotService, RobotSnapshotService>();
|
||||
services.AddScoped<IRobotTaskRobotStatusService, RobotTaskRobotStatusService>();
|
||||
services.AddScoped<IRobotTaskDetailsService, RobotTaskDetailsService>();
|
||||
|
||||
services.AddScoped<IUnitService, UnitService>();
|
||||
services.AddScoped<UnitCacheService>();
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using AutoMapper;
|
||||
using PARR.Domain.DTOs.Shared;
|
||||
using PARR.Domain.Entities.JobGroupEntities;
|
||||
|
||||
namespace PARR.Core.Infrastructure.Mapping.Shared
|
||||
{
|
||||
public class JobGroupResultMappingProfile: Profile
|
||||
{
|
||||
public JobGroupResultMappingProfile()
|
||||
{
|
||||
CreateMap<JobGroup, JobGroupShortResult>()
|
||||
.Include<JobGroup, JobGroupResult>();
|
||||
|
||||
CreateMap<JobGroup, JobGroupResult>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,4 +25,8 @@
|
||||
<!-- Разрешаем Castle DynamicProxy (Moq / NSubstitute) видеть internal классы PARR.Core -->
|
||||
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Services\RobotStatusDetails\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -273,6 +273,9 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
if (templatesToRename.Count == 0)
|
||||
return tasks;
|
||||
|
||||
// Создаем словарь маппинга TemplateId -> OldName.
|
||||
var templateIdToOldName = templatesToRename.ToDictionary(t => t.TemplateId, t => t.OldName);
|
||||
|
||||
// Ищем конфигурации роботов для СТАРЫХ шаблонов (которые переименовываются) по ИД, смотрим, можем ли взять их в работу
|
||||
var renameTemplateIds = templatesToRename.Select(t => t.TemplateId).ToList();
|
||||
var renameTasks = await _robotConfigurationRepository.Get()
|
||||
@@ -299,10 +302,16 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
{
|
||||
_logger.LogDebug("Найдено старых заданий на переименование с ошибками: {ErrorCount}. Ставим ошибку целевым (новым) заданиям.", errorTasks.Count);
|
||||
|
||||
var errorTemplateNames = errorTasks.Select(t => t.Template!.Name).ToHashSet();
|
||||
//var errorTemplateNames = errorTasks.Select(t => t.Template!.Name).ToHashSet();
|
||||
// Берем OldName из словаря
|
||||
var errorOldNames = errorTasks
|
||||
.Where(t => templateIdToOldName.ContainsKey(t.TemplateId))
|
||||
.Select(t => templateIdToOldName[t.TemplateId])
|
||||
.ToHashSet();
|
||||
|
||||
// Берем целевые таски, находим в них задания которым надо поставить ошибку
|
||||
tasksToSetErrorStatus = tasks
|
||||
.Where(t => errorTemplateNames.Contains(t.TemplateName))
|
||||
.Where(t => errorOldNames.Contains(t.TemplateName))
|
||||
.Select(t => t.TaskId)
|
||||
.ToList();
|
||||
|
||||
@@ -319,39 +328,71 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
var endDate = DateTimeOffset.UtcNow.Add(-_settingsFromDb.RobotWaitTime);
|
||||
|
||||
// Фильтруем старые задачи, которые МОЖНО взять в работу. Смотрим статусы роботов, можно взять в работу, только если (RobotStatus == Wait) или (InpRogress но которые еще не просрочены)
|
||||
var allowedRenameTasks = renameTasks.Where(t =>
|
||||
var allowedTasks = renameTasks.Where(t =>
|
||||
t.RobotStatusCode == (int)RobotStatusEnum.Wait
|
||||
|| (t.RobotStatusCode == (int)RobotStatusEnum.InProgress
|
||||
&& t.AttemptsNumber < _settingsFromDb.RobotAttemptsNumber
|
||||
&& t.LastRobotStatusUpdated < endDate)
|
||||
).ToList();
|
||||
|
||||
// Словарь для поиска подменной задачи по имени шаблона.
|
||||
// GroupBy + First на случай, если в бд есть дубликаты, но такого быть не может
|
||||
var renameTasksToDictionary = allowedRenameTasks
|
||||
.GroupBy(t => t.Template!.Name)
|
||||
// Проверим StatusTypeId у старых шаблонов в процессе переименования
|
||||
// 1. Находим задачи переименования, у которых StatusTypeId шаблона != Used
|
||||
var invalidRenameTasks = allowedTasks
|
||||
.Where(t => t.Template != null && t.Template.StatusTypeId != TemplateStatusTypeEnum.Used)
|
||||
.ToList();
|
||||
|
||||
// 2. Создаем словарь для быстрого поиска и логирования: OldName -> StatusTypeId
|
||||
var invalidOldNamesWithStatus = invalidRenameTasks
|
||||
.Where(t => templateIdToOldName.ContainsKey(t.TemplateId))
|
||||
.Select(t => new { OldName = templateIdToOldName[t.TemplateId], StatusTypeId = t.Template!.StatusTypeId })
|
||||
.ToDictionary(x => x.OldName, x => x.StatusTypeId);
|
||||
|
||||
// 3. Оставляем для подмены только те задачи, у которых StatusTypeId == 0
|
||||
var validAllowedTasks = allowedTasks
|
||||
.Where(t => t.Template != null && t.Template.StatusTypeId == TemplateStatusTypeEnum.Used)
|
||||
.ToList();
|
||||
|
||||
// Формируем список заданий
|
||||
var originalCount = tasks.Count;
|
||||
var errorTaskIdsSet = tasksToSetErrorStatus.ToHashSet();
|
||||
var errorCount = errorTaskIdsSet.Count;
|
||||
|
||||
// Создаем словарь подмены ТОЛЬКО из валидных задач (где StatusTypeId == Used)
|
||||
var renameTasksToDictionary = validAllowedTasks
|
||||
.Where(t => templateIdToOldName.ContainsKey(t.TemplateId))
|
||||
.GroupBy(t => templateIdToOldName[t.TemplateId])
|
||||
.ToDictionary(
|
||||
t => t.Key,
|
||||
t => new RobotTaskDetails(t.First().Id, t.First().Template!.Name, t.First().Template!.NextRun)
|
||||
g => g.Key,
|
||||
g => new RobotTaskDetails(g.First().Id, g.First().Template!.Name, g.First().Template!.NextRun)
|
||||
);
|
||||
|
||||
var errorTaskIdsSet = tasksToSetErrorStatus.ToHashSet();
|
||||
|
||||
// Создаем итоговый список
|
||||
// Проходим по ИСХОДНОМУ списку, чтобы сохранить порядок сортировки
|
||||
var finalTasks = new List<RobotTaskDetails>(tasks.Count);
|
||||
int replacedCount = 0;
|
||||
int errorCount = errorTaskIdsSet.Count;
|
||||
int excludedByStatusCount = 0; // Счетчик для логов
|
||||
|
||||
// Проходим по ИСХОДНОМУ списку, чтобы сохранить его порядок сортировки
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
// Если задаче нужно поставить ошибку, просто пропускаем ее (она не попадет в итоговый список)
|
||||
// 1. Если задаче нужно поставить ошибку, пропускаем ее
|
||||
if (errorTaskIdsSet.Contains(task.TaskId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Если для этого имени шаблона есть разрешенная задача на переименование - вставляем ее на место текущей
|
||||
// 2. Если этот шаблон связан с переименованием, но у старого шаблона StatusTypeId != Used
|
||||
if (invalidOldNamesWithStatus.TryGetValue(task.TemplateName, out var badStatusId))
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Задача для шаблона '{TemplateName}' (TaskId: {TaskId}) ИСКЛЮЧЕНА из выдачи. " +
|
||||
"Связанный шаблон в процессе переименования имеет недопустимый StatusTypeId = {StatusTypeId} (ожидалось Used). " +
|
||||
"Исходная задача также не выполняется.",
|
||||
task.TemplateName, task.TaskId, badStatusId);
|
||||
|
||||
excludedByStatusCount++;
|
||||
continue; // Не добавляем ни старую, ни новую задачу в итоговый список
|
||||
}
|
||||
|
||||
// 3. Если для этого имени шаблона есть разрешенная задача на переименование (и она валидна) - вставляем ее
|
||||
if (renameTasksToDictionary.TryGetValue(task.TemplateName, out var renameTask))
|
||||
{
|
||||
finalTasks.Add(renameTask);
|
||||
@@ -364,11 +405,12 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
// Логируем итоговую статистику трансформации пула задач
|
||||
_logger.LogInformation(
|
||||
"Трансформация пула задач (Rename). Исходных: {OriginalCount}. Отклонено (Error): {ErrorCount}. " +
|
||||
"Заменено на старые: {ReplacedCount}. Итого к выдаче: {FinalCount}",
|
||||
tasks.Count, errorCount, replacedCount, finalTasks.Count
|
||||
);
|
||||
"Трансформация пула задач завершена. Исходных: {OriginalCount} шт. " +
|
||||
"Отклонено (ошибка): {ErrorCount} шт. Исключено (невалидный StatusTypeId): {ExcludedCount} шт. " +
|
||||
"Заменено на старые: {ReplacedCount} шт. Итого к выдаче: {FinalCount} шт.",
|
||||
originalCount, errorCount, excludedByStatusCount, replacedCount, finalTasks.Count);
|
||||
|
||||
// Возвращаем без дополнительной сортировки по NextRun. Порядок сохранен начального списка
|
||||
return finalTasks;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Services.RobotTaskDetailsServices.Interfaces;
|
||||
using PARR.Domain.DTOs.RobotTaskDetails;
|
||||
using PARR.Domain.DTOs.Shared;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.Core.Services.RobotTaskDetailsServices.Implementations
|
||||
{
|
||||
internal class RobotTaskDetailsService : IRobotTaskDetailsService
|
||||
{
|
||||
private readonly ILogger<RobotTaskDetailsService> _logger;
|
||||
private readonly IRobotConfigurationRepository _robotConfigurationRepository;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IRobotRepository _robotRepository;
|
||||
private readonly ITaskStatusRepository _taskStatusRepository;
|
||||
|
||||
public RobotTaskDetailsService(
|
||||
ILogger<RobotTaskDetailsService> logger,
|
||||
IRobotConfigurationRepository robotConfigurationRepository,
|
||||
IMapper mapper,
|
||||
IRobotRepository robotRepository,
|
||||
ITaskStatusRepository taskStatusRepository
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_robotConfigurationRepository = robotConfigurationRepository;
|
||||
_mapper = mapper;
|
||||
_robotRepository = robotRepository;
|
||||
_taskStatusRepository = taskStatusRepository;
|
||||
}
|
||||
|
||||
public async Task<RobotTaskDetailsResult> GetDetailsAsync(RobotsEnum robot, TaskStatusEnum task)
|
||||
{
|
||||
var details = await _robotConfigurationRepository.Get()
|
||||
.Where(t => t.RobotCode == (int)robot && t.TaskStatusCode == (int)task)
|
||||
.GroupBy(t => t.Template!.Job!.Group)
|
||||
.Select(t => new
|
||||
{
|
||||
JobGroup = t.Key,
|
||||
TemplatesCount = t.Count()
|
||||
}).ToListAsync();
|
||||
|
||||
var robotObj = await _robotRepository.Get()
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(t => t.Code == (int)robot);
|
||||
|
||||
var taskObj = await _taskStatusRepository.Get()
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(t => t.Code == (int)task);
|
||||
|
||||
var result = new RobotTaskDetailsResult
|
||||
{
|
||||
Robot = _mapper.Map<RobotResult>(robotObj),
|
||||
Task = _mapper.Map<RobotTaskStatusResult>(taskObj),
|
||||
Details = details.Select(t => new RobotTaskGroupDetailsResult
|
||||
{
|
||||
JobGroup = _mapper.Map<JobGroupShortResult>(t.JobGroup),
|
||||
TemplatesCount = t.TemplatesCount
|
||||
}).OrderBy(t => t.JobGroup.GroupName).ToList()
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using PARR.Domain.DTOs.RobotTaskDetails;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.Core.Services.RobotTaskDetailsServices.Interfaces
|
||||
{
|
||||
public interface IRobotTaskDetailsService
|
||||
{
|
||||
/// <summary>
|
||||
/// Список групп работ по заданиям робота
|
||||
/// </summary>
|
||||
/// <param name="robot"></param>
|
||||
/// <param name="task"></param>
|
||||
/// <returns></returns>
|
||||
Task<RobotTaskDetailsResult> GetDetailsAsync(RobotsEnum robot, TaskStatusEnum task);
|
||||
}
|
||||
}
|
||||
18
PARR.Domain/DTOs/RobotTaskDetails/RobotTaskDetailsResult.cs
Normal file
18
PARR.Domain/DTOs/RobotTaskDetails/RobotTaskDetailsResult.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using PARR.Domain.DTOs.Shared;
|
||||
|
||||
namespace PARR.Domain.DTOs.RobotTaskDetails
|
||||
{
|
||||
public record RobotTaskDetailsResult
|
||||
{
|
||||
public RobotResult Robot { get; init; }
|
||||
public RobotTaskStatusResult Task { get; init; }
|
||||
|
||||
public List<RobotTaskGroupDetailsResult> Details { get; init; }
|
||||
}
|
||||
|
||||
public record RobotTaskGroupDetailsResult
|
||||
{
|
||||
public JobGroupShortResult JobGroup { get; init; }
|
||||
public int TemplatesCount { get; init; }
|
||||
}
|
||||
}
|
||||
15
PARR.Domain/DTOs/Shared/JobGroupResult.cs
Normal file
15
PARR.Domain/DTOs/Shared/JobGroupResult.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace PARR.Domain.DTOs.Shared
|
||||
{
|
||||
public record JobGroupShortResult
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public required string GroupName { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
public record JobGroupResult : JobGroupShortResult
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user