diff --git a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor index eee6d5cf2..8e05f6d87 100644 --- a/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor +++ b/src/AzureIoTHub.Portal.Client/Pages/EdgeModels/EdgeModelDetailPage.razor @@ -4,6 +4,7 @@ @using AzureIoTHub.Portal.Client.Pages.EdgeModels.EdgeModule @using System.Net.Http.Headers @using AzureIoTHub.Portal.Models.v10 +@using AzureIoTHub.Portal.Shared.Models.v10 @using AzureIoTHub.Portal.Client.Validators @using Microsoft.AspNetCore.Components.WebAssembly.Authentication @@ -69,6 +70,43 @@ + + + + System Modules + + + + + + + + + + + Module name + Image URI + Detail + + + + + + + + + + + Detail + + + + + + + + + @@ -362,4 +400,19 @@ EdgeModel.EdgeRoutes.Add(new IoTEdgeRoute()); } } + + private async Task ShowSystemModuleDetail(EdgeModelSystemModule systemModule) + { + var parameters = new DialogParameters(); + parameters.Add("module", systemModule); + + DialogOptions options = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, CloseButton = true }; + + var result = await DialogService.Show(systemModule.Name, parameters, options).Result; + + if (result.Cancelled) + { + return; + } + } } diff --git a/src/AzureIoTHub.Portal.Server/Services/ConfigService.cs b/src/AzureIoTHub.Portal.Server/Services/ConfigService.cs index c79520e6d..8ba2518a5 100644 --- a/src/AzureIoTHub.Portal.Server/Services/ConfigService.cs +++ b/src/AzureIoTHub.Portal.Server/Services/ConfigService.cs @@ -12,6 +12,7 @@ namespace AzureIoTHub.Portal.Server.Services using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models.v10; using AzureIoTHub.Portal.Server.Helpers; + using AzureIoTHub.Portal.Shared.Models.v10; using Extensions; using Microsoft.Azure.Devices; using Microsoft.Azure.Devices.Common.Extensions; @@ -105,6 +106,48 @@ public async Task> GetConfigModuleList(string modelId) return moduleList; } + public async Task> GetModelSystemModule(string modelId) + { + var configList = await GetIoTEdgeConfigurations(); + + var config = configList.FirstOrDefault((x) => x.Id.StartsWith(modelId, StringComparison.Ordinal)); + + if (config == null) + { + throw new InternalServerErrorException("Config does not exist."); + } + + var moduleList = new List(); + + // Details of every modules are stored within the EdgeAgent module data + if (config.Content.ModulesContent != null + && config.Content.ModulesContent.TryGetValue("$edgeAgent", out var edgeAgentModule) + && edgeAgentModule.TryGetValue("properties.desired", out var edgeAgentDesiredProperties)) + { + // Converts the object to a JObject to access its properties more easily + if (edgeAgentDesiredProperties is not JObject modObject) + { + throw new InvalidOperationException($"Could not parse properties.desired for the configuration id {config.Id}"); + } + + // Adds regular modules to the list of modules + if (modObject.TryGetValue("systemModules", out var modules)) + { + foreach (var newModule in modules.Values().Select(module => ConfigHelper.CreateGatewayModule(config, module))) + { + moduleList.Add(new EdgeModelSystemModule(newModule.ModuleName) + { + ImageUri = newModule.ImageURI, + EnvironmentVariables = newModule.EnvironmentVariables, + ContainerCreateOptions = newModule.ContainerCreateOptions, + }); + } + } + } + + return moduleList; + } + public async Task> GetConfigRouteList(string modelId) { var configList = await GetIoTEdgeConfigurations(); diff --git a/src/AzureIoTHub.Portal.Server/Services/EdgeModelService.cs b/src/AzureIoTHub.Portal.Server/Services/EdgeModelService.cs index c46006d6b..56d9cfb50 100644 --- a/src/AzureIoTHub.Portal.Server/Services/EdgeModelService.cs +++ b/src/AzureIoTHub.Portal.Server/Services/EdgeModelService.cs @@ -164,6 +164,7 @@ public async Task GetEdgeModel(string modelId) } var modules = await this.configService.GetConfigModuleList(modelId); + var sysModules = await this.configService.GetModelSystemModule(modelId); var routes = await this.configService.GetConfigRouteList(modelId); var commands = this.commandRepository.GetAll().Where(x => x.EdgeDeviceModelId == modelId).ToList(); @@ -176,7 +177,8 @@ public async Task GetEdgeModel(string modelId) Name = edgeModelEntity.Name, Description = edgeModelEntity.Description, EdgeModules = modules, - EdgeRoutes = routes + EdgeRoutes = routes, + SystemModules = sysModules, }; foreach (var command in commands) diff --git a/src/AzureIoTHub.Portal.Server/Services/IConfigService.cs b/src/AzureIoTHub.Portal.Server/Services/IConfigService.cs index bb2b48a4f..03fb4bdd1 100644 --- a/src/AzureIoTHub.Portal.Server/Services/IConfigService.cs +++ b/src/AzureIoTHub.Portal.Server/Services/IConfigService.cs @@ -6,6 +6,7 @@ namespace AzureIoTHub.Portal.Server.Services using System.Collections.Generic; using System.Threading.Tasks; using AzureIoTHub.Portal.Models.v10; + using AzureIoTHub.Portal.Shared.Models.v10; using Microsoft.Azure.Devices; public interface IConfigService @@ -28,6 +29,8 @@ public interface IConfigService Task> GetConfigModuleList(string modelId); + Task> GetModelSystemModule(string modelId); + Task> GetConfigRouteList(string modelId); } } diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/EdgeModels/EdgeModelDetailPageTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/EdgeModels/EdgeModelDetailPageTest.cs index d54508e9e..7d4f4e3b2 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/EdgeModels/EdgeModelDetailPageTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/EdgeModels/EdgeModelDetailPageTest.cs @@ -12,6 +12,7 @@ namespace AzureIoTHub.Portal.Tests.Unit.Client.Pages.EdgeModels using AzureIoTHub.Portal.Client.Pages.EdgeModels.EdgeModule; using AzureIoTHub.Portal.Client.Services; using AzureIoTHub.Portal.Models.v10; + using AzureIoTHub.Portal.Shared.Models.v10; using AzureIoTHub.Portal.Tests.Unit.UnitTests.Bases; using Bunit; using Bunit.TestDoubles; @@ -295,6 +296,60 @@ public void ClickShowEditEdgeModuleDialogShouldDisplayEditModuleDialogAndReturnI cut.WaitForAssertion(() => MockRepository.VerifyAll()); } + [Test] + public void ClickOnShowSystemModuleDetailShouldShowDialog() + { + // Arrange + _ = SetupLoadEdgeModel(); + + var mockDialogReference = MockRepository.Create(); + _ = mockDialogReference.Setup(c => c.Result).ReturnsAsync(DialogResult.Ok("Ok")); + + _ = this.mockDialogService + .Setup(c => c.Show(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(mockDialogReference.Object); + + // Act + var cut = RenderComponent(ComponentParameter.CreateParameter("ModelID", this.mockEdgeModleId)); + + cut.WaitForAssertion(() => Assert.AreEqual(1, cut.FindAll("#editSystModuleButton_edgeAgent").Count)); + var editEdgeAgentButton = cut.WaitForElement("#editSystModuleButton_edgeAgent"); + + cut.WaitForElement($"#{nameof(EdgeModelSystemModule.ImageUri)}").Change("image/test"); + + editEdgeAgentButton.Click(); + + // Assert + cut.WaitForAssertion(() => MockRepository.VerifyAll()); + } + + [Test] + public void ClickOnShowSystemModuleDetailShouldShowDialogAndReturnIfAborted() + { + // Arrange + _ = SetupLoadEdgeModel(); + + var mockDialogReference = MockRepository.Create(); + _ = mockDialogReference.Setup(c => c.Result).ReturnsAsync(DialogResult.Cancel()); + + _ = this.mockDialogService + .Setup(c => c.Show(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(mockDialogReference.Object); + + // Act + var cut = RenderComponent(ComponentParameter.CreateParameter("ModelID", this.mockEdgeModleId)); + + cut.WaitForAssertion(() => Assert.AreEqual(1, cut.FindAll("#editSystModuleButton_edgeAgent").Count)); + var editEdgeAgentButton = cut.WaitForElement("#editSystModuleButton_edgeAgent"); + + cut.WaitForElement($"#{nameof(EdgeModelSystemModule.ImageUri)}").Change("image/test"); + + editEdgeAgentButton.Click(); + + // Assert + cut.WaitForAssertion(() => MockRepository.VerifyAll()); + } + [Test] public void DeleteAvatarShouldRemoveTheImage() { diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs index bf2f4d0ad..c4532b859 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs @@ -611,6 +611,131 @@ public void WhenPropertiesDesiredIsInWrongFormatGetConfigModuleListShouldThrowIn this.mockRepository.VerifyAll(); } + [Test] + public async Task GetModelSystemModuleReturnList() + { + // Arrange + var configService = CreateConfigsServices(); + + var configTest = new Configuration(Guid.NewGuid().ToString()); + var listConfig = new List() + { + configTest + }; + + var edgeAgentPropertiesDesired = new EdgeAgentPropertiesDesired(); + var modules = new Dictionary() + { + {"module test 01", new ConfigModule() }, + }; + + edgeAgentPropertiesDesired.Modules = modules; + + var mockConfigEnumerator = this.mockRepository.Create>(); + + configTest.Content.ModulesContent = new Dictionary>() + { + { + "$edgeAgent", new Dictionary() + { + { + "properties.desired", JObject.Parse(JsonConvert.SerializeObject(edgeAgentPropertiesDesired)) + } + } + } + }; + + _ = mockConfigEnumerator.Setup(x => x.GetEnumerator()).Returns(listConfig.GetEnumerator); + + _ = this.mockRegistryManager.Setup(c => c.GetConfigurationsAsync(It.Is(x => x == 0))) + .ReturnsAsync(mockConfigEnumerator.Object); + + // Act + var result = await configService.GetModelSystemModule(configTest.Id); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(2, result.Count); + + this.mockRepository.VerifyAll(); + } + + [Test] + public void WhenGetConfigIsNullGetModelSystemModuleShouldThrowInternalServerErrorException() + { + // Arrange + var configService = CreateConfigsServices(); + + var configTest = new Configuration(Guid.NewGuid().ToString()); + var listConfig = new List() + { + }; + + var mockConfigEnumerator = this.mockRepository.Create>(); + + _ = mockConfigEnumerator.Setup(x => x.GetEnumerator()).Returns(listConfig.GetEnumerator); + + _ = this.mockRegistryManager.Setup(c => c.GetConfigurationsAsync(It.Is(x => x == 0))) + .ReturnsAsync(mockConfigEnumerator.Object); + + // Act + var result = async () => await configService.GetModelSystemModule(configTest.Id); + + // Assert + _ = result.Should().ThrowAsync(); + + this.mockRepository.VerifyAll(); + } + + [Test] + public void WhenPropertiesDesiredIsInWrongFormatConfigIsNullGetModelSystemModuleShouldThrowInvalidOperationException() + { + // Arrange + var configService = CreateConfigsServices(); + + var configTest = new Configuration(Guid.NewGuid().ToString()); + var listConfig = new List() + { + configTest + }; + + var edgeAgentPropertiesDesired = new EdgeAgentPropertiesDesired(); + var modules = new Dictionary() + { + {"module test 01", new ConfigModule() }, + {"module test 02", new ConfigModule() } + }; + + edgeAgentPropertiesDesired.Modules = modules; + + var mockConfigEnumerator = this.mockRepository.Create>(); + + configTest.Content.ModulesContent = new Dictionary>() + { + { + "$edgeAgent", new Dictionary() + { + { + "properties.desired", edgeAgentPropertiesDesired + } + } + } + }; + + _ = mockConfigEnumerator.Setup(x => x.GetEnumerator()).Returns(listConfig.GetEnumerator); + + _ = this.mockRegistryManager.Setup(c => c.GetConfigurationsAsync(It.Is(x => x == 0))) + .ReturnsAsync(mockConfigEnumerator.Object); + + // Act + var result = async () => await configService.GetModelSystemModule(configTest.Id); + + // Assert + _ = result.Should().ThrowAsync(); + + this.mockRepository.VerifyAll(); + } + [Test] public async Task GetConfigRouteListShouldReturnAList() { diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs index a0deeebf7..17d0be16e 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs @@ -17,6 +17,7 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services using AzureIoTHub.Portal.Models.v10; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Services; + using AzureIoTHub.Portal.Shared.Models.v10; using AzureIoTHub.Portal.Tests.Unit.UnitTests.Bases; using FluentAssertions; using Microsoft.AspNetCore.Http; @@ -98,6 +99,7 @@ public async Task GetEdgeModelShouldReturnValueAsync() // Arrange var expectedModules = Fixture.CreateMany(2).ToList(); var expectedRoutes = Fixture.CreateMany(2).ToList(); + var expectedSysModule = Fixture.CreateMany(2).ToList(); var expectedImageUri = Fixture.Create(); var expectedEdgeDeviceModel = new IoTEdgeModel() @@ -107,7 +109,8 @@ public async Task GetEdgeModelShouldReturnValueAsync() ImageUrl = expectedImageUri, Description = Guid.NewGuid().ToString(), EdgeModules = expectedModules, - EdgeRoutes = expectedRoutes + EdgeRoutes = expectedRoutes, + SystemModules = expectedSysModule }; var expectedCommands = Fixture.CreateMany(5).Select(command => @@ -127,8 +130,13 @@ public async Task GetEdgeModelShouldReturnValueAsync() _ = this.mockConfigService.Setup(x => x.GetConfigModuleList(It.IsAny())) .ReturnsAsync(expectedModules); + + _ = this.mockConfigService.Setup(x => x.GetModelSystemModule(It.IsAny())) + .ReturnsAsync(expectedSysModule); + _ = this.mockConfigService.Setup(x => x.GetConfigRouteList(It.IsAny())) .ReturnsAsync(expectedRoutes); + _ = this.mockEdgeDeviceModelCommandRepository.Setup(x => x.GetAll()) .Returns(expectedCommands);