Skip to content

Commit

Permalink
Make device tags searchable in the search panel on DevicesListPage (#397
Browse files Browse the repository at this point in the history
)

* Added searchable tags to DeviceListItem object

* Added tags field in the search panel

* Fix unit tests

* Fix code scanning errors
  • Loading branch information
audserraCGI authored Mar 9, 2022
1 parent 08a547f commit 5b80e6f
Show file tree
Hide file tree
Showing 13 changed files with 198 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,15 @@ public async Task GetList_StateUnderTest_ExpectedBehavior()
Tags = twinCollection
}));

this.mockDeviceTwinMapper.Setup(c => c.CreateDeviceListItem(It.IsAny<Twin>()))
.Returns<Twin>(x => new DeviceListItem
this.mockDeviceTwinMapper.Setup(c => c.CreateDeviceListItem(It.IsAny<Twin>(),It.IsAny<IEnumerable<string>>()))
.Returns<Twin,IEnumerable<string>>((x,y) => new DeviceListItem
{
DeviceID = x.DeviceId
});

this.mockDeviceTagService.Setup(c => c.GetAllSearchableTagsNames())
.Returns(new List<string>());

// Act
var result = await devicesController.Get();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,15 @@ public async Task GetList_StateUnderTest_ExpectedBehavior()
Tags = twinCollection
}));

this.mockDeviceTwinMapper.Setup(c => c.CreateDeviceListItem(It.IsAny<Twin>()))
.Returns<Twin>(x => new DeviceListItem
this.mockDeviceTwinMapper.Setup(c => c.CreateDeviceListItem(It.IsAny<Twin>(), It.IsAny<IEnumerable<string>>()))
.Returns<Twin,IEnumerable<string>>((x,y) => new DeviceListItem
{
DeviceID = x.DeviceId
});

this.mockDeviceTagService.Setup(c => c.GetAllSearchableTagsNames())
.Returns(new List<string>());

// Act
var result = await devicesController.Get();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,12 @@ public void CreateDeviceListItem_StateUnderTest_ExpectedBehavior()

twin.Tags[nameof(DeviceDetails.ModelId).ToCamelCase()] = "000-000-001";

twin.Tags["assetId"] = Guid.NewGuid().ToString();
twin.Tags["locationCode"] = Guid.NewGuid().ToString();
List<string> tagsNames = new List<string>() { "assetId", "locationCode" };

// Act
var result = deviceTwinMapper.CreateDeviceListItem(twin);
var result = deviceTwinMapper.CreateDeviceListItem(twin, tagsNames);

// Assert
Assert.IsNotNull(result);
Expand All @@ -148,6 +152,50 @@ public void CreateDeviceListItem_StateUnderTest_ExpectedBehavior()
Assert.IsFalse(result.IsConnected);
Assert.IsFalse(result.IsEnabled);

foreach (string tagName in tagsNames)
{
Assert.AreEqual(twin.Tags[tagName.ToCamelCase()].ToString(), result.CustomTags[tagName]);
}

Assert.AreEqual(DateTime.MinValue, result.StatusUpdatedTime);

this.mockRepository.VerifyAll();
}

public void CreateDeviceListItem_NullTagList_ExpectedBehavior()
{
// Arrange
var deviceTwinMapper = this.CreateDeviceTwinMapper();

Twin twin = new Twin
{
DeviceId = Guid.NewGuid().ToString()
};

twin.Tags[nameof(DeviceDetails.ModelId).ToCamelCase()] = "000-000-001";

List<string> tagsNames = null;

twin.Properties.Reported["DevAddr"] = Guid.NewGuid().ToString();

this.mockDeviceModelImageManager.Setup(c => c.ComputeImageUri(It.Is<string>(c => c.Equals("000-000-001", StringComparison.OrdinalIgnoreCase))))
.Returns("http://fake.local/000-000-001")
.Verifiable();

// Act
var result = deviceTwinMapper.CreateDeviceDetails(twin, tagsNames);

// Assert
Assert.IsNotNull(result);

Assert.IsFalse(result.IsConnected);
Assert.IsFalse(result.IsEnabled);

Assert.AreEqual(twin.Tags[nameof(DeviceDetails.ModelId).ToCamelCase()].ToString(), result.ModelId);

Assert.IsEmpty(result.CustomTags);

Assert.AreEqual("http://fake.local/000-000-001", result.ImageUrl);
Assert.AreEqual(DateTime.MinValue, result.StatusUpdatedTime);

this.mockRepository.VerifyAll();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public void CreateDeviceListItem_StateUnderTest_ExpectedBehavior()
.Returns(expectedModelImageUri);

// Act
var result = loRaDeviceTwinMapper.CreateDeviceListItem(twin);
var result = loRaDeviceTwinMapper.CreateDeviceListItem(twin,null);

// Assert
Assert.IsNotNull(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ public class DevicesListPageTests
private FakeNavigationManager mockNavigationManager;
private MockHttpMessageHandler mockHttpClient;

private string apiBaseUrl = "/api/Devices";
private string apiSettingsBaseUrl = "/api/settings/lora";
private readonly string apiBaseUrl = "/api/Devices";
private readonly string apiSettingsBaseUrl = "/api/settings/lora";
private readonly string apiTagsBaseUrl = "/api/settings/device-tags";

[SetUp]
public void SetUp()
Expand Down Expand Up @@ -133,6 +134,10 @@ public async Task When_ResetFilterButton_Click_Should_Clear_Filters()
.When(HttpMethod.Get, apiSettingsBaseUrl)
.RespondJson(true);

this.mockHttpClient
.When(HttpMethod.Get, apiTagsBaseUrl)
.RespondJson(new object[0]);

var cut = RenderComponent<DeviceListPage>();

cut.Find("#searchID").NodeValue = Guid.NewGuid().ToString();
Expand Down Expand Up @@ -166,6 +171,10 @@ public async Task When_AddNewDevice_Click_Should_Navigate_To_New_Device_Page(str
.When(HttpMethod.Get, apiSettingsBaseUrl)
.RespondJson(true);

this.mockHttpClient
.When(HttpMethod.Get, apiTagsBaseUrl)
.RespondJson(new object[0]);

var cut = RenderComponent<DeviceListPage>();
await Task.Delay(100);

Expand All @@ -191,6 +200,10 @@ public async Task When_Refresh_Click_Should_Reload_From_Api()
.When(HttpMethod.Get, apiSettingsBaseUrl)
.RespondJson(true);

this.mockHttpClient
.When(HttpMethod.Get, apiTagsBaseUrl)
.RespondJson(new object[0]);

var cut = RenderComponent<DeviceListPage>();
cut.WaitForAssertion(() => cut.Find($"#tableRefreshButton"), 1.Seconds());

Expand Down Expand Up @@ -222,6 +235,10 @@ public void When_Lora_Feature_disable_device_detail_link_Should_not_contain_lora
.When(HttpMethod.Get, apiSettingsBaseUrl)
.RespondJson(false);

this.mockHttpClient
.When(HttpMethod.Get, apiTagsBaseUrl)
.RespondJson(new object[0]);

var cut = RenderComponent<DeviceListPage>();
_ = cut.WaitForElements(".detail-link");

Expand Down Expand Up @@ -250,6 +267,10 @@ public void When_Lora_Feature_enable_device_detail_link_Should_contain_lora()
.When(HttpMethod.Get, apiSettingsBaseUrl)
.RespondJson(true);

this.mockHttpClient
.When(HttpMethod.Get, apiTagsBaseUrl)
.RespondJson(new object[0]);

var cut = RenderComponent<DeviceListPage>();
_ = cut.WaitForElements(".detail-link");

Expand Down
114 changes: 72 additions & 42 deletions src/AzureIoTHub.Portal/Client/Pages/Devices/DeviceListPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,50 @@
<MudExpansionPanels>
<MudExpansionPanel Text="Search panel">
<MudGrid>
<MudItem xs="12" md="6">
<MudItem xs="12" md="12">
<MudTextField @bind-Value="searchID" Placeholder="DeviceID / DeviceName" id="searchID"></MudTextField>
</MudItem>
<MudItem xs="12" md="6">
@*<MudTextField @bind-Value="searchName" Placeholder="Name"></MudTextField>*@
</MudItem>
<MudItem sm="12" md="6">
<MudText>Status</MudText>
<MudRadioGroup @bind-SelectedOption="@searchStatus" Style="display:flex;align-items:baseline" id="searchStatus">
<MudItem md="4" sm="12">
<MudRadio Option="@("true")" Color="Color.Primary" id="searchStatusEnabled">Enabled</MudRadio>
</MudItem>
<MudItem md="4" sm="12">
<MudRadio Option="@("false")" Color="Color.Primary" id="searchDisabled">Disabled</MudRadio>
</MudItem>
<MudItem md="4" sm="12">
<MudRadio Option="@("")" Color="Color.Secondary" id="searchStatusAll">All</MudRadio>
</MudItem>
</MudRadioGroup>

</MudItem>
<MudItem xs="12" md="6">
<MudText>Connection state</MudText>
<MudRadioGroup @bind-SelectedOption="@searchState" Style="display:flex;align-items:baseline" >
<MudItem md="4" sm="12">
<MudRadio Option="@("true")" Color="Color.Primary" id="searchStateConnected">Connected</MudRadio>
</MudItem>
<MudItem md="4" sm="12">
<MudRadio Option="@("false")" Color="Color.Primary" id="searchStateDisconnected">Disconnected</MudRadio>
</MudItem>
<MudItem md="4" sm="12">
<MudRadio Option="@("")" Color="Color.Secondary" id="searchStateAll">All</MudRadio>
@foreach (DeviceTag tag in TagList)
{
if (tag.Searchable)
{
<MudItem xs="12" md="6">
<MudTextField @bind-Value="searchTags[tag.Name]" Placeholder="@tag.Label"></MudTextField>
</MudItem>
</MudRadioGroup>
</MudItem>
}
}
<MudGrid>
<MudItem sm="12" md="6">
<MudText>Status</MudText>
<MudRadioGroup @bind-SelectedOption="@searchStatus" Style="display:flex;align-items:baseline" id="searchStatus">
<MudItem md="4" sm="12">
<MudRadio Option="@("true")" Color="Color.Primary" id="searchStatusEnabled">Enabled</MudRadio>
</MudItem>
<MudItem md="4" sm="12">
<MudRadio Option="@("false")" Color="Color.Primary" id="searchDisabled">Disabled</MudRadio>
</MudItem>
<MudItem md="4" sm="12">
<MudRadio Option="@("")" Color="Color.Secondary" id="searchStatusAll">All</MudRadio>
</MudItem>
</MudRadioGroup>

</MudItem>
<MudItem xs="12" md="6">
<MudText>Connection state</MudText>
<MudRadioGroup @bind-SelectedOption="@searchState" Style="display:flex;align-items:baseline">
<MudItem md="4" sm="12">
<MudRadio Option="@("true")" Color="Color.Primary" id="searchStateConnected">Connected</MudRadio>
</MudItem>
<MudItem md="4" sm="12">
<MudRadio Option="@("false")" Color="Color.Primary" id="searchStateDisconnected">Disconnected</MudRadio>
</MudItem>
<MudItem md="4" sm="12">
<MudRadio Option="@("")" Color="Color.Secondary" id="searchStateAll">All</MudRadio>
</MudItem>
</MudRadioGroup>
</MudItem>
</MudGrid>

<MudItem xs="12">
<MudButton Variant="Variant.Outlined" Color="Color.Success" Style="margin:0.5em;" id="searchButton">Search</MudButton>
Expand All @@ -72,9 +81,9 @@
else
{
<MudItem xs="12">
<MudTable Items="@result" Dense=true Breakpoint="Breakpoint.Sm" Hover=true Bordered=true Striped=true
ro
Filter="new Func<DeviceListItem, bool>(FilterFunc)">
<MudTable Items="@result" Dense=true Breakpoint="Breakpoint.Sm" Hover=true Bordered=true Striped=true
ro
Filter="new Func<DeviceListItem, bool>(FilterFunc)">
<ColGroup>
<col style="width: 5%;" />
<col style="width: 40%;" />
Expand Down Expand Up @@ -106,7 +115,7 @@
<MudTd DataLabel="Device" Style="word-break: break-all;">
<a class="detail-link" href="@($"devices/{@context.DeviceID}{((@context.SupportLoRaFeatures && isLoraEnable) ? "?isLora=true": "")}")">
@(string.IsNullOrEmpty(context.DeviceName) ? context.DeviceID : context.DeviceName)
</a>
</a>
</MudTd>

<MudTd DataLabel="Status" Style="text-align: center">
Expand Down Expand Up @@ -139,12 +148,12 @@
<MudTd DataLabel="Delete" Style="text-align: center">
<MudIconButton Color="Color.Default" Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" OnClick="@(e => DeleteDevice(context))"></MudIconButton>
</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager PageSizeOptions="@pageSizeOptions"></MudTablePager>
</PagerContent>
</MudTable>
</MudItem>
</RowTemplate>
<PagerContent>
<MudTablePager PageSizeOptions="@pageSizeOptions"></MudTablePager>
</PagerContent>
</MudTable>
</MudItem>
}

<MudItem xs="12">
Expand All @@ -158,14 +167,26 @@
private string searchID = "";
private string searchStatus = "";
private string searchState = "";
private Dictionary<string, string> searchTags = new();

private bool isLoraEnable;

private IEnumerable<DeviceTag> TagList { get; set; } = new List<DeviceTag>();

private int[] pageSizeOptions = new int[] { 2, 5, 10 };

protected override async Task OnInitializedAsync()
{
isLoraEnable = await this.Http.GetFromJsonAsync<bool>("api/settings/lora");
await LoadDevices();

// Gets the custom tags that can be searched via the panel
TagList = await Http.GetFromJsonAsync<List<DeviceTag>>($"/api/settings/device-tags");
foreach (DeviceTag tag in TagList)
{
if (tag.Searchable)
searchTags.Add(tag.Name, "");
}
}

private void AddDevice()
Expand Down Expand Up @@ -198,6 +219,7 @@
searchID = "";
searchStatus = "";
searchState = "";
searchTags = searchTags.ToDictionary(tag => tag.Key, tag => "");
}

/// <summary>
Expand Down Expand Up @@ -232,8 +254,16 @@
bool flagStatus = checkFlag(item.IsEnabled.ToString(), searchStatus);
bool flagState = checkFlag(item.IsConnected.ToString(), searchState);

// Checks the different customized flags
List<bool> flagTags = new();
foreach (KeyValuePair<string, string> tag in searchTags)
{
flagTags.Add(checkFlag(item.CustomTags[tag.Key], tag.Value));
}
bool flagTag = !flagTags.Contains(false);

// Current row is shown only if all the flags are true
if ((flagID || flagName) && flagStatus && flagState)
if ((flagID || flagName) && flagStatus && flagState && flagTag)
return true;

return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ public DevicesControllerBase(
public virtual async Task<IEnumerable<TListItem>> Get()
{
var items = await this.devicesService.GetAllDevice(excludeDeviceType: "LoRa Concentrator");
var tagList = this.deviceTagService.GetAllSearchableTagsNames();

return items.Select(this.deviceTwinMapper.CreateDeviceListItem);
return items.Select(c => this.deviceTwinMapper.CreateDeviceListItem(c,tagList));
}

/// <summary>
Expand Down
14 changes: 12 additions & 2 deletions src/AzureIoTHub.Portal/Server/Mappers/DeviceTwinMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,17 @@ public DeviceDetails CreateDeviceDetails(Twin twin, IEnumerable<string> tags)
};
}

public DeviceListItem CreateDeviceListItem(Twin twin)
public DeviceListItem CreateDeviceListItem(Twin twin, IEnumerable<string> tags)
{
Dictionary<string, string> customTags = new Dictionary<string, string>();
if(tags != null)
{
foreach(string tag in tags)
{
customTags.Add(tag, Helpers.DeviceHelper.RetrieveTagValue(twin, tag));
}
}

return new DeviceListItem
{
DeviceID = twin.DeviceId,
Expand All @@ -54,7 +63,8 @@ public DeviceListItem CreateDeviceListItem(Twin twin)
StatusUpdatedTime = twin.StatusUpdatedTime.GetValueOrDefault(DateTime.MinValue),
DeviceName = Helpers.DeviceHelper.RetrieveTagValue(twin, nameof(DeviceListItem.DeviceName)),
ImageUrl = this.deviceModelImageManager.ComputeImageUri(Helpers.DeviceHelper.RetrieveTagValue(twin, nameof(DeviceDetails.ModelId))),
SupportLoRaFeatures = bool.Parse(Helpers.DeviceHelper.RetrieveTagValue(twin, nameof(DeviceListItem.SupportLoRaFeatures)) ?? "false")
SupportLoRaFeatures = bool.Parse(Helpers.DeviceHelper.RetrieveTagValue(twin, nameof(DeviceListItem.SupportLoRaFeatures)) ?? "false"),
CustomTags = customTags
};
}

Expand Down
Loading

0 comments on commit 5b80e6f

Please sign in to comment.