Merge pull request 'Бэк /Критические сообщения' (#323) from feature/#37333881-critical-message into dev

Reviewed-on: https://test.digitaldrilling.ru:8443/DDrilling/AsbCloudServer/pulls/323
Reviewed-by: Степанов Дмитрий Александрович <da.stepanov@digitaldrilling.ru>
This commit is contained in:
Никита Фролов 2024-09-12 17:20:27 +05:00
commit d37d2fa317
20 changed files with 827 additions and 205 deletions

View File

@ -0,0 +1,29 @@
namespace AsbCloudApp.Data.SAUB;
/// <summary>
/// dto для события телеметрии
/// </summary>
public class TelemetryEventDto
{
/// <summary>
/// ключ события
/// </summary>
public object Id { get; set; }
/// <summary>
/// ключ категории
/// </summary>
public int IdCategory { get; set; }
/// <summary>
/// шаблон сообщения
/// </summary>
public string MessageTemplate { get; set; } = null!;
/// <summary>
/// метод формирования текста сообщения по шаблону
/// </summary>
/// <param name="args">аргументы, которые подставляются в шаблон сообщения</param>
/// <returns></returns>
public string MakeMessageText(string?[] args) => string.Format(MessageTemplate, args);
}

View File

@ -51,4 +51,9 @@ public class TelemetryMessageDto : IId
/// аргумент №3 для подстановки в шаблон сообщения
/// </summary>
public string? Arg3 { get; set; }
/// <summary>
/// ключ телеметрии
/// </summary>
public int IdTelemetry { get; set; }
}

View File

@ -0,0 +1,28 @@

namespace AsbCloudApp.Data;
/// <summary>
/// dto для отображения статистики сообщений
/// </summary>
public class StatCriticalMessageDto
{
/// <summary>
/// ключ скважины
/// </summary>
public int IdWell { get; set; }
/// <summary>
/// ключ категории
/// </summary>
public int? IdCategory { get; set; }
/// <summary>
/// шаблон сообщения
/// </summary>
public string? MessageTemplate { get; set; }
/// <summary>
/// количество сообщений
/// </summary>
public int MessagesCount { get; set; }
}

View File

@ -0,0 +1,30 @@
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Requests;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Repositories;
/// <summary>
/// Репозиторий по работе с событиями
/// </summary>
public interface IEventRepository
{
/// <summary>
/// получение списка событий по параметрам запроса
/// </summary>
/// <param name="request">параметры запроса</param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TelemetryEventDto>> GetAsync(TelemetryEventRequest request, CancellationToken token);
/// <summary>
/// Сохранить. Добавить или заменить.
/// </summary>
/// <param name="uid"></param>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
Task UpsertAsync(string uid, IEnumerable<EventDto> dtos, CancellationToken token);
}

View File

@ -0,0 +1,41 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Repositories;
/// <summary>
/// Репозиторий по работе с сообщениями панели оператора
/// </summary>
public interface IMessageRepository : ITelemetryDataEditorService
{
/// <summary>
/// Получить сообщения по параметрам
/// </summary>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<PaginationContainer<MessageDto>> GetPaginatedMessagesAsync(MessageTelemetryRequest request, CancellationToken token);
/// <summary>
/// Получить сообщения по параметрам
/// </summary>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TelemetryMessageDto>> GetMessagesAsync(MessageTelemetryRequest request, CancellationToken token);
/// <summary>
/// Метод для сохранения сообщения от панели
/// </summary>
/// <param name="uid"></param>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
Task InsertAsync(string uid, IEnumerable<TelemetryMessageDto> dtos,
CancellationToken token);
}

View File

@ -1,5 +1,8 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB;
using System;
using System.Collections.Generic;
using System.Linq;
namespace AsbCloudApp.Requests;
@ -11,7 +14,7 @@ public class MessageRequestBase : RequestBase
/// <summary>
/// категория
/// </summary>
public IEnumerable<int>? Categoryids { get; set; }
public IEnumerable<int>? IdsCategories { get; set; }
/// <summary>
@ -29,29 +32,21 @@ public class MessageRequestBase : RequestBase
/// </summary>
public string? SearchString { get; set; }
}
/// <summary>
/// параметры для запроса списка сообщений (с id скважины)
/// </summary>
public class MessageRequest : MessageRequestBase
{
/// <summary>
/// id скважины
///
/// </summary>
public int IdWell { get; set; }
public MessageRequestBase()
{
}
/// <summary>
/// параметры для запроса списка сообщений (с id скважины)
/// копирующий конструктор
/// </summary>
/// <param name="request"></param>
/// <param name="idWell"></param>
public MessageRequest(MessageRequestBase request, int idWell)
public MessageRequestBase(MessageRequestBase request)
{
this.IdWell = idWell;
this.Categoryids = request.Categoryids;
this.IdsCategories = request.IdsCategories;
this.Begin = request.Begin;
this.End = request.End;
this.SearchString = request.SearchString;
@ -62,3 +57,55 @@ public class MessageRequest : MessageRequestBase
}
}
/// <summary>
/// параметры для запроса списка сообщений (с ids скважин)
/// </summary>
public class MessageRequest : MessageRequestBase
{
/// <summary>
/// ids скважин
/// </summary>
public IEnumerable<int> IdsWell { get; set; } = null!;
/// <summary>
/// параметры для запроса списка сообщений (с ids скважин)
/// </summary>
/// <param name="request"></param>
/// <param name="idsWell"></param>
public MessageRequest(MessageRequestBase request, IEnumerable<int> idsWell) : base(request)
{
this.IdsWell = idsWell;
}
}
/// <summary>
/// параметры запроса для получения списка сообщений телеметрии
/// </summary>
public class MessageTelemetryRequest : MessageRequestBase
{
/// <summary>
/// события
/// </summary>
public IEnumerable<TelemetryEventDto> Events { get; set; }= Enumerable.Empty<TelemetryEventDto>();
/// <summary>
/// телеметрии
/// </summary>
public IEnumerable<TelemetryDto> Telemetries { get; set; } = Enumerable.Empty<TelemetryDto>();
/// <inheritdoc/>
public MessageTelemetryRequest(
MessageRequestBase request,
IEnumerable<TelemetryEventDto> events,
IEnumerable<TelemetryDto> telemetries) : base(request)
{
Events = events;
Telemetries = telemetries;
}
/// <inheritdoc/>
public MessageTelemetryRequest(MessageRequestBase request)
: base(request) { }
}

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AsbCloudApp.Requests;
/// <summary>
/// параметры запроса для получения событий телеметрии
/// </summary>
public class TelemetryEventRequest
{
/// <summary>
/// ключи телеметрии
/// </summary>
public IEnumerable<int> IdsTelemetries { get; set; } = null!;
/// <summary>
/// ключи категорий
/// </summary>
public IEnumerable<int>? IdsCategories { get; set; }
/// <summary>
/// строка поиска
/// </summary>
public string? SearchString { get; set; }
}

View File

@ -1,22 +0,0 @@
using AsbCloudApp.Data.SAUB;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Services;
/// <summary>
/// Сервис сохранения списка сообщений от панелей
/// </summary>
public interface IEventService
{
/// <summary>
/// Сохранить. Добавить или заменить.
/// </summary>
/// <param name="uid"></param>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
Task UpsertAsync(string uid, IEnumerable<EventDto> dtos,
CancellationToken token = default);
}

View File

@ -1,5 +1,4 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Requests;
using System.Collections.Generic;
using System.Threading;
@ -8,25 +7,24 @@ using System.Threading.Tasks;
namespace AsbCloudApp.Services;
/// <summary>
/// Сервис сообщений панели оператора
/// Сервис сообщений
/// </summary>
public interface IMessageService : ITelemetryDataEditorService
public interface IMessageService
{
/// <summary>
/// Получить сообщения по параметрам
/// Получить статистику сообщений по параметрам запроса
/// </summary>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<PaginationContainer<MessageDto>> GetMessagesAsync(MessageRequest request, CancellationToken token);
Task<IEnumerable<StatCriticalMessageDto>> GetStatAsync(MessageRequest request, CancellationToken token);
/// <summary>
/// Метод для сохранения сообщения от панели
/// Получить PaginationContainer с сообщениями по параметрам
/// </summary>
/// <param name="uid"></param>
/// <param name="dtos"></param>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
Task InsertAsync(string uid, IEnumerable<TelemetryMessageDto> dtos,
CancellationToken token);
Task<PaginationContainer<MessageDto>> GetPaginatedMessagesAsync(MessageRequest request, CancellationToken token);
}

View File

@ -47,6 +47,12 @@ public interface ITelemetryService
/// <returns></returns>
TelemetryBaseDto? GetOrDefaultTelemetryByIdWell(int idWell);
/// <summary>
/// получить список телеметрии по ключам скважин
/// </summary>
/// <param name="idsWells">ключи скважин</param>
/// <returns></returns>
IEnumerable<TelemetryDto> GetOrDefaultTelemetriesByIdsWells(IEnumerable<int> idsWells);
/// <summary>
/// получить диапазон дат за которые есть данные
/// </summary>

View File

@ -0,0 +1,270 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services;
using NSubstitute;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace AsbCloudInfrastructure.Tests.Services;
public class MessageServiceTest
{
private static readonly List<TelemetryDto> telemetries = new List<TelemetryDto>()
{
new TelemetryDto()
{
Id = 1,
IdWell = 1,
TimeZone = new SimpleTimezoneDto()
{
Hours = 5
}
},
new TelemetryDto()
{
Id = 2,
IdWell = 2,
TimeZone = new SimpleTimezoneDto()
{
Hours = 5
}
}
};
private static readonly List<TelemetryEventDto> events = new List<TelemetryEventDto>()
{
new TelemetryEventDto()
{
Id = 1,
IdCategory = 1,
MessageTemplate = "Шаблон сообщения 1 категории 1"
},
new TelemetryEventDto()
{
Id = 2,
IdCategory = 1,
MessageTemplate = "Шаблон сообщения 2 категории 1"
},
new TelemetryEventDto()
{
Id = 3,
IdCategory = 1,
MessageTemplate = "Шаблон сообщения 3 категории 1"
},
new TelemetryEventDto()
{
Id = 4,
IdCategory = 2,
MessageTemplate = "Шаблон сообщения 1 категории 2"
},
new TelemetryEventDto()
{
Id = 5,
IdCategory = 2,
MessageTemplate = "Шаблон сообщения 2 категории 2"
},
new TelemetryEventDto()
{
Id = 6,
IdCategory = 3,
MessageTemplate = "Шаблон сообщения 1 категории 3"
}
};
/// <summary>
/// Структура:
/// Телеметрия 1:
/// Категория 1:
/// Событие 1:
/// Сообщение 1
/// Сообщение 2
/// Сообщение 3
/// Событие 2:
/// Сообщение 1
/// Сообщение 2
/// Сообщение 3
/// Событие 3:
/// Сообщение 1
/// Сообщение 2
/// Сообщение 3
/// Категория 2:
/// Событие 2:
/// Сообщение 1
/// Сообщение 2
/// Событие 3:
/// Сообщение 1
/// Сообщение 2
/// Телеметрия 2
/// Категория 3:
/// Событие 3:
/// Сообщение 1
/// Сообщение 2
/// Сообщение 3
///
/// </summary>
private static readonly List<TelemetryMessageDto> messages = new List<TelemetryMessageDto>()
{
new TelemetryMessageDto()
{
Id = 1,
IdEvent = 1,
IdTelemetry = 1
},
new TelemetryMessageDto()
{
Id = 2,
IdEvent = 1,
IdTelemetry = 1
},
new TelemetryMessageDto()
{
Id = 3,
IdEvent = 1,
IdTelemetry = 1
},
new TelemetryMessageDto()
{
Id = 4,
IdEvent = 2,
IdTelemetry = 1
},
new TelemetryMessageDto()
{
Id = 5,
IdEvent = 2,
IdTelemetry = 1
},
new TelemetryMessageDto()
{
Id = 6,
IdEvent = 2,
IdTelemetry = 1
},
new TelemetryMessageDto()
{
Id = 7,
IdEvent = 3,
IdTelemetry = 1
},
new TelemetryMessageDto()
{
Id = 8,
IdEvent = 3,
IdTelemetry = 1
},
new TelemetryMessageDto()
{
Id = 9,
IdEvent = 3,
IdTelemetry = 1
},
new TelemetryMessageDto()
{
Id = 10,
IdEvent = 4,
IdTelemetry = 1
},
new TelemetryMessageDto()
{
Id = 11,
IdEvent = 4,
IdTelemetry = 1
},
new TelemetryMessageDto()
{
Id = 12,
IdEvent = 5,
IdTelemetry = 1
},
new TelemetryMessageDto()
{
Id = 13,
IdEvent = 5,
IdTelemetry = 1
},
new TelemetryMessageDto()
{
Id = 14,
IdEvent = 6,
IdTelemetry = 2
},
new TelemetryMessageDto()
{
Id = 15,
IdEvent = 6,
IdTelemetry = 2
},
new TelemetryMessageDto()
{
Id = 16,
IdEvent = 6,
IdTelemetry = 2
},
};
private readonly IMessageService messageServiceMock = Substitute.For<IMessageService>();
private readonly IMessageRepository messageRepositoryMock = Substitute.For<IMessageRepository>();
private readonly IEventRepository eventRepositoryMock = Substitute.For<IEventRepository>();
private readonly ITelemetryService telemetryServiceMock = Substitute.For<ITelemetryService>();
public MessageServiceTest()
{
messageServiceMock = new MessageService(messageRepositoryMock, telemetryServiceMock, eventRepositoryMock);
telemetryServiceMock
.GetOrDefaultTelemetriesByIdsWells(Arg.Any<IEnumerable<int>>())
.Returns(telemetries);
eventRepositoryMock
.GetAsync(Arg.Any<TelemetryEventRequest>(), Arg.Any<CancellationToken>())
.Returns(events);
messageRepositoryMock
.GetMessagesAsync(Arg.Any<MessageTelemetryRequest>(), Arg.Any<CancellationToken>())
.Returns(messages);
}
[Fact]
public async Task GetStatMessages_ShouldReturn_Success()
{
//act
var baseRequest = new MessageRequestBase() { };
var messageRequest = new MessageRequest(baseRequest, [1]);
var result = await messageServiceMock.GetStatAsync(messageRequest, CancellationToken.None);
Assert.Equal(6, result.Count());
AssertStatByEvent(result, 1, events[0], 3);
AssertStatByEvent(result, 1, events[1], 3);
AssertStatByEvent(result, 1, events[2], 3);
AssertStatByEvent(result, 1, events[3], 2);
AssertStatByEvent(result, 1, events[4], 2);
AssertStatByEvent(result, 2, events[5], 3);
}
private void AssertStatByEvent(IEnumerable<StatCriticalMessageDto> result, int idWell, TelemetryEventDto eventDto, int count)
{
var eventStats = result
.Where(x => x.IdWell == idWell)
.Where(x => x.IdCategory == eventDto.IdCategory)
.Where(x => x.MessageTemplate == eventDto.MessageTemplate)
.ToArray();
Assert.Single(eventStats);
var eventStat = eventStats.First();
Assert.Equal(count, eventStat.MessagesCount);
}
}

View File

@ -293,9 +293,10 @@ public static class DependencyInjection
services.AddTransient<IAuthService, AuthService>();
services.AddTransient<IDepositRepository, DepositRepository>();
services.AddTransient<IDrillingProgramService, DrillingProgramService>();
services.AddTransient<IEventService, EventService>();
services.AddTransient<IEventRepository, EventRepository>();
services.AddTransient<FileService>();
services.AddTransient<IMeasureService, MeasureService>();
services.AddTransient<IMessageRepository, MessageRepository>();
services.AddTransient<IMessageService, MessageService>();
services.AddTransient<IOperationsStatService, OperationsStatService>();
services.AddTransient<IReportService, ReportService>();

View File

@ -0,0 +1,71 @@
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb;
using AsbCloudDb.Model;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Repository;
public class EventRepository : IEventRepository
{
private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache;
private readonly ITelemetryService telemetryService;
public EventRepository(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService)
{
this.db = db;
this.memoryCache = memoryCache;
this.telemetryService = telemetryService;
}
public async Task<IEnumerable<TelemetryEventDto>> GetAsync(TelemetryEventRequest request, CancellationToken token)
{
var allEvents = await memoryCache.GetOrCreateBasicAsync(db.Set<TelemetryEvent>(), token);
var events = allEvents.Where(e => request.IdsTelemetries.Contains(e.IdTelemetry));
if (request.IdsCategories?.Any() == true)
events = events.Where(e => request.IdsCategories.Contains(e.IdCategory));
if (!string.IsNullOrEmpty(request.SearchString))
events = events.Where(e => e.MessageTemplate.Contains(request.SearchString, StringComparison.OrdinalIgnoreCase));
var dtos = events.Select(e =>
{
var dto = new TelemetryEventDto();
dto.Id = e.IdEvent;
dto.IdCategory = e.IdCategory;
dto.MessageTemplate = e.MessageTemplate;
return dto;
});
return dtos;
}
public async Task UpsertAsync(string uid, IEnumerable<EventDto> dtos,
CancellationToken token = default)
{
if (!dtos.Any())
return;
var telemetry = telemetryService.GetOrCreateTelemetryByUid(uid);
var entities = dtos.Select(dto => new TelemetryEvent
{
IdEvent = dto.Id,
IdTelemetry = telemetry.Id,
IdCategory = dto.IdCategory,
MessageTemplate = dto.Message
});
await db.Database.ExecInsertOrUpdateAsync(db.TelemetryEvents, entities, token);
memoryCache.DropBasic<TelemetryEvent>();
}
}

View File

@ -0,0 +1,89 @@
using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services;
public class MessageService : IMessageService
{
private readonly IMessageRepository messageRepository;
private readonly ITelemetryService telemetryService;
private readonly IEventRepository eventRepository;
public MessageService(
IMessageRepository messageRepository,
ITelemetryService telemetryService,
IEventRepository eventRepository)
{
this.messageRepository = messageRepository;
this.telemetryService = telemetryService;
this.eventRepository = eventRepository;
}
public async Task<PaginationContainer<MessageDto>> GetPaginatedMessagesAsync(MessageRequest request, CancellationToken token)
{
var messageTelemetryRequest = await CreateMessageTelemetryRequest(request, token);
var result = await messageRepository.GetPaginatedMessagesAsync(messageTelemetryRequest, token);
return result;
}
public async Task<IEnumerable<StatCriticalMessageDto>> GetStatAsync(MessageRequest request, CancellationToken token)
{
var messageTelemetryRequest = await CreateMessageTelemetryRequest(request, token);
var telemetryMessages = await messageRepository.GetMessagesAsync(messageTelemetryRequest, token);
var groupedMessages = telemetryMessages.GroupBy(x => new { x.IdTelemetry, x.IdEvent });
var events = messageTelemetryRequest.Events.ToDictionary(e => e.Id);
var idsWellsDict = messageTelemetryRequest.Telemetries.ToDictionary(e => e.Id, e => e.IdWell);
var result = groupedMessages
.Select(m => new
{
IdWell = idsWellsDict.GetValueOrDefault(m.Key.IdTelemetry),
events.GetValueOrDefault(m.Key.IdEvent)?.IdCategory,
events.GetValueOrDefault(m.Key.IdEvent)?.MessageTemplate,
Count = m.Count(),
})
.Where(m => m.IdCategory != null)
.Where(m => m.IdWell != null)
.Where(m => m.MessageTemplate != null)
.GroupBy(m => new { m.IdWell, m.IdCategory, m.MessageTemplate })
.Select(m => new StatCriticalMessageDto()
{
IdWell = m.First().IdWell!.Value,
IdCategory = m.Key.IdCategory,
MessageTemplate = m.Key.MessageTemplate,
MessagesCount = m.Select(z => z.Count).Sum()
});
return result;
}
public async Task<MessageTelemetryRequest> CreateMessageTelemetryRequest(MessageRequest request, CancellationToken token)
{
var telemetries = telemetryService.GetOrDefaultTelemetriesByIdsWells(request.IdsWell);
var telemetryEventRequest = new TelemetryEventRequest()
{
IdsTelemetries = telemetries.Select(t => t.Id),
IdsCategories = request.IdsCategories,
SearchString = request.SearchString,
};
var events = await eventRepository.GetAsync(telemetryEventRequest, token);
var messageTelemetryRequest = new MessageTelemetryRequest(request, events, telemetries);
return messageTelemetryRequest;
}
}

View File

@ -1,45 +0,0 @@
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services;
using AsbCloudDb;
using AsbCloudDb.Model;
using Microsoft.Extensions.Caching.Memory;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.SAUB;
public class EventService : IEventService
{
private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache;
private readonly ITelemetryService telemetryService;
public EventService(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService)
{
this.db = db;
this.memoryCache = memoryCache;
this.telemetryService = telemetryService;
}
public async Task UpsertAsync(string uid, IEnumerable<EventDto> dtos,
CancellationToken token = default)
{
if (!dtos.Any())
return;
var telemetry = telemetryService.GetOrCreateTelemetryByUid(uid);
var entities = dtos.Select(dto => new TelemetryEvent
{
IdEvent = dto.Id,
IdTelemetry = telemetry.Id,
IdCategory = dto.IdCategory,
MessageTemplate = dto.Message
});
var result = await db.Database.ExecInsertOrUpdateAsync(db.TelemetryEvents, entities, token);
memoryCache.DropBasic<TelemetryEvent>();
}
}

View File

@ -1,12 +1,17 @@
using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb;
using AsbCloudDb.Model;
using DocumentFormat.OpenXml.Bibliography;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
@ -16,20 +21,31 @@ using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services.SAUB;
public class MessageService : IMessageService
public class MessageRepository : IMessageRepository
{
private readonly IAsbCloudDbContext db;
private readonly IMemoryCache memoryCache;
private readonly ITelemetryService telemetryService;
public MessageService(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService)
public MessageRepository(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService)
{
this.db = db;
this.memoryCache = memoryCache;
this.telemetryService = telemetryService;
}
public async Task<PaginationContainer<MessageDto>> GetMessagesAsync(MessageRequest request, CancellationToken token)
public async Task<IEnumerable<TelemetryMessageDto>> GetMessagesAsync(MessageTelemetryRequest request, CancellationToken token)
{
var query = BuildQuery(request, token);
var entities = await query.ToArrayAsync(token);
var dtos = entities.Select(m => m.Adapt<TelemetryMessageDto>());
return dtos;
}
public async Task<PaginationContainer<MessageDto>> GetPaginatedMessagesAsync(MessageTelemetryRequest request, CancellationToken token)
{
var result = new PaginationContainer<MessageDto>
{
@ -37,36 +53,86 @@ public class MessageService : IMessageService
Take = request.Take ?? 32,
};
var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(request.IdWell);
if (telemetry is null)
if (request.Telemetries.IsNullOrEmpty() || request.Events.IsNullOrEmpty())
return result;
var allEvents = await memoryCache.GetOrCreateBasicAsync(db.Set<TelemetryEvent>(), token);
var events = allEvents.Where(e => e.IdTelemetry == telemetry.Id);
var query = BuildQuery(request, token);
if (!events.Any())
return result;
var query = db.TelemetryMessages.Where(m => m.IdTelemetry == telemetry.Id)
.OrderBy(m => m.DateTime).AsNoTracking();
if (request.Categoryids?.Any() == true || !string.IsNullOrEmpty(request.SearchString))
if (request.SortFields?.Any() == true)
{
if (!string.IsNullOrEmpty(request.SearchString))
events = events.Where(e => e.MessageTemplate.Contains(request.SearchString, StringComparison.OrdinalIgnoreCase));
if (request.Categoryids?.Any() == true)
events = events.Where(e => request.Categoryids.ToList().Contains(e.IdCategory));
var eventIds = events.Select(e => e.IdEvent);
if (!eventIds.Any())
return result;
query = query.Where(m => eventIds.Contains(m.IdEvent));
query = query.SortBy(request.SortFields);
}
query = query.OrderByDescending(m => m.DateTime);
result.Count = query.Count();
var messagesList = await query
.Skip(result.Skip)
.Take(result.Take)
.AsNoTracking()
.ToArrayAsync(token);
if (messagesList.Count() == 0)
return result;
var allUsers = await memoryCache.GetOrCreateBasicAsync(db.Set<TelemetryUser>(), token);
var users = allUsers.Where(u => request.Telemetries!.Select(t => t.Id).Contains(u.IdTelemetry));
if (!request.Events.Any())
return result;
var eventsDict = request.Events.ToDictionary(x => x.Id);
var usersDict = users.ToDictionary(x => x.IdUser, x => x);
var messagesDtoList = new List<MessageDto>();
foreach (var message in messagesList)
{
var messageDto = new MessageDto
{
Id = message.Id,
WellDepth = message.WellDepth,
};
var telemetry = request.Telemetries.Where(t => t.Id == message.IdTelemetry).FirstOrDefault();
if(telemetry != null && telemetry.TimeZone != null)
messageDto.DateTime = message.DateTime.ToOffset(TimeSpan.FromHours(telemetry.TimeZone.Hours));
if (message.IdTelemetryUser is not null)
{
if (usersDict.TryGetValue((int)message.IdTelemetryUser, out TelemetryUser? user))
{
messageDto.User = user.MakeDisplayName();
}
else
messageDto.User = message.IdTelemetryUser.ToString();
}
if (eventsDict.TryGetValue(message.IdEvent, out TelemetryEventDto? e))
{
messageDto.CategoryId = e.IdCategory;
messageDto.Message = e.MakeMessageText([
message.Arg0,
message.Arg1,
message.Arg2,
message.Arg3
]);
}
messagesDtoList.Add(messageDto);
}
result.Items = result.Items.Concat(messagesDtoList);
return result;
}
public IQueryable<TelemetryMessage> BuildQuery(MessageTelemetryRequest request, CancellationToken token)
{
var idsTelemetries = request.Telemetries.Select(t => t.Id);
var eventIds = request.Events.Select(e => e.Id);
var query = db.TelemetryMessages
.Where(m => idsTelemetries.Contains(m.IdTelemetry))
.Where(m => eventIds.Contains(m.IdEvent));
if (request.Begin is not null)
{
@ -80,63 +146,13 @@ public class MessageService : IMessageService
query = query.Where(m => m.DateTime <= endUtc);
}
result.Count = query.Count();
query = query.OrderByDescending(m => m.DateTime);
if (request.SortFields?.Any() == true)
{
query = query.SortBy(request.SortFields);
}
var messagesList = await query.Skip(result.Skip)
.Take(result.Take).AsNoTracking()
.ToListAsync(token).ConfigureAwait(false);
if (messagesList.Count == 0)
return result;
var allUsers = await memoryCache.GetOrCreateBasicAsync(db.Set<TelemetryUser>(), token);
var users = allUsers.Where(u => u.IdTelemetry == telemetry.Id);
var eventsDict = events.ToDictionary(x => x.IdEvent, x => x);
var usersDict = users.ToDictionary(x => x.IdUser, x => x);
var messagesDtoList = new List<MessageDto>();
var timezone = telemetryService.GetTimezone(telemetry.Id);
foreach (var message in messagesList)
{
var messageDto = new MessageDto
{
Id = message.Id,
WellDepth = message.WellDepth
};
messageDto.DateTime = message.DateTime.ToOffset(TimeSpan.FromHours(timezone.Hours));
if (message.IdTelemetryUser is not null)
{
if (usersDict.TryGetValue((int)message.IdTelemetryUser, out TelemetryUser? user))
{
messageDto.User = user.MakeDisplayName();
}
else
messageDto.User = message.IdTelemetryUser.ToString();
}
if (eventsDict.TryGetValue(message.IdEvent, out TelemetryEvent? e))
{
messageDto.CategoryId = e.IdCategory;
messageDto.Message = e.MakeMessageText(message);
}
messagesDtoList.Add(messageDto);
}
result.Items = result.Items.Concat(messagesDtoList);
return result;
return query;
}
public Task InsertAsync(string uid, IEnumerable<TelemetryMessageDto> dtos,
CancellationToken token = default)
CancellationToken token = default)
{
if (!dtos.Any())
return Task.CompletedTask;

View File

@ -21,7 +21,7 @@ public class TelemetryDataEditorService : ITelemetryDataEditorService
/// <param name="dataSaubService"></param>
/// <param name="dataSpinService"></param>
/// <param name="dataSaubStatRepository"></param>
/// <param name="messageService"></param>
/// <param name="messageRepository"></param>
/// <param name="drillTestRepository"></param>
/// <param name="limitingParameterRepository"></param>
/// <param name="detectedOperationRepository"></param>
@ -36,7 +36,7 @@ public class TelemetryDataEditorService : ITelemetryDataEditorService
ITelemetryDataSaubService dataSaubService,
ITelemetryDataService<TelemetryDataSpinDto> dataSpinService,
IDataSaubStatRepository dataSaubStatRepository,
IMessageService messageService,
IMessageRepository messageRepository,
IDrillTestRepository drillTestRepository,
ILimitingParameterRepository limitingParameterRepository,
IDetectedOperationRepository detectedOperationRepository,
@ -54,7 +54,7 @@ public class TelemetryDataEditorService : ITelemetryDataEditorService
dataSaubService,
dataSpinService,
dataSaubStatRepository,
messageService,
messageRepository,
drillTestRepository,
limitingParameterRepository,
detectedOperationRepository,

View File

@ -144,15 +144,32 @@ public class TelemetryService : ITelemetryService
public TelemetryBaseDto? GetOrDefaultTelemetryByIdWell(int idWell)
{
var entity = GetTelemetryCache()
.FirstOrDefault(t => t.Well?.Id == idWell);
var dto = GetOrDefaultTelemetriesByIdsWells([idWell])
.FirstOrDefault();
if (entity?.Well?.Timezone is not null && entity.TimeZone.Hours != entity.Well.Timezone.Hours)
{
entity.TimeZone = entity.Well.Timezone;
//TODO: выдаем предупреждение!
}
return entity?.Adapt<TelemetryBaseDto>();
return dto;
}
public IEnumerable<TelemetryDto> GetOrDefaultTelemetriesByIdsWells(IEnumerable<int> idsWells)
{
var entities = GetTelemetryCache()
.Where(t => t.Well != null)
.Where(t => idsWells.Contains(t.Well!.Id))
.Select(t => {
t.TimeZone = t.TimeZone.Hours != t.Well!.Timezone.Hours ? t.Well.Timezone : t.TimeZone;
return t;
});
var dtos = entities.Select(t => {
var dto = t.Adapt<TelemetryDto>();
dto.IdWell = t.Well?.Id;
return dto;
});
return dtos;
}
public TelemetryDto GetOrCreateTelemetryByUid(string uid)

View File

@ -1,7 +1,10 @@
using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@ -14,13 +17,18 @@ namespace AsbCloudWebApi.Controllers.SAUB;
[ApiController]
public class MessageController : ControllerBase
{
private readonly IMessageService messageService;
private readonly IMessageRepository messageRepository;
private readonly IWellService wellService;
private readonly IMessageService messageService;
public MessageController(IMessageService messageService, IWellService wellService)
public MessageController(
IMessageRepository messageRepository,
IWellService wellService,
IMessageService messageService)
{
this.messageService = messageService;
this.messageRepository = messageRepository;
this.wellService = wellService;
this.messageService = messageService;
}
/// <summary>
@ -46,17 +54,22 @@ public class MessageController : ControllerBase
if (request.Take > 1024)
return this.ValidationBadRequest(nameof(request.Take), "limit mast be less then 1024");
var requestToService = new MessageRequest(request, idWell);
var result = await messageService.GetMessagesAsync(
requestToService,
token)
.ConfigureAwait(false);
var messageRequest = new MessageRequest(request, new int[] { idWell });
var result = await messageService.GetPaginatedMessagesAsync(messageRequest, token);
if (result is null || result.Count == 0)
return NoContent();
return Ok(result);
}
[HttpGet("/api/serviceOperation/[controller]")]
[ProducesResponseType(typeof(IEnumerable<StatCriticalMessageDto>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> Get([FromQuery] MessageRequest request, CancellationToken token)
{
var result = await messageService.GetStatAsync(request, token);
return Ok(result);
}
/// <summary>

View File

@ -1,11 +1,11 @@
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using AsbCloudWebApi.SignalR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -20,21 +20,21 @@ namespace AsbCloudWebApi.Controllers.SAUB;
public class TelemetryController : ControllerBase
{
private readonly ITelemetryService telemetryService;
private readonly IMessageService messageService;
private readonly IEventService eventService;
private readonly IMessageRepository messageRepository;
private readonly IEventRepository eventRepository;
private readonly ITelemetryUserService telemetryUserService;
private readonly IHubContext<TelemetryHub> telemetryHubContext;
public TelemetryController(
ITelemetryService telemetryService,
IMessageService messageService,
IEventService eventService,
IMessageRepository messageRepository,
IEventRepository eventRepository,
ITelemetryUserService telemetryUserService,
IHubContext<TelemetryHub> telemetryHubContext)
{
this.telemetryService = telemetryService;
this.messageService = messageService;
this.eventService = eventService;
this.messageRepository = messageRepository;
this.eventRepository = eventRepository;
this.telemetryUserService = telemetryUserService;
this.telemetryHubContext = telemetryHubContext;
}
@ -49,7 +49,7 @@ public class TelemetryController : ControllerBase
{
var from = DateTimeOffset.UtcNow.AddDays(-1);
var stream = await telemetryService.GetTelemetriesInfoByLastData(from, token);
return File(stream, "text/csv", $"Software versions by active telemetries from {from :yy-MM-dd hh-mm}.csv");
return File(stream, "text/csv", $"Software versions by active telemetries from {from:yy-MM-dd hh-mm}.csv");
}
/// <summary>
@ -79,7 +79,7 @@ public class TelemetryController : ControllerBase
CancellationToken token)
{
var idWell = telemetryService.GetIdWellByTelemetryUid(uid);
await messageService.InsertAsync(uid, dtos, token).ConfigureAwait(false);
await messageRepository.InsertAsync(uid, dtos, token).ConfigureAwait(false);
if (dtos.Any())
await Task.Run(() => telemetryHubContext.Clients.Group($"well_{idWell}")
@ -99,7 +99,7 @@ public class TelemetryController : ControllerBase
public async Task<IActionResult> PostEventsAsync(string uid, [FromBody] List<EventDto> events,
CancellationToken token)
{
await eventService.UpsertAsync(uid, events, token)
await eventRepository.UpsertAsync(uid, events, token)
.ConfigureAwait(false);
return Ok();
}