Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make device tags searchable in the search panel on DevicesListPage #397

Merged
merged 4 commits into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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