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

Feature/1770 enable search sort device models #1781

Merged
merged 11 commits into from
Feb 23, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@

namespace AzureIoTHub.Portal.Application.Services
{
using System.Collections.Generic;
using System.Threading.Tasks;
using AzureIoTHub.Portal.Models.v10;
using AzureIoTHub.Portal.Shared.Models;
using AzureIoTHub.Portal.Shared.Models.v1._0;
using AzureIoTHub.Portal.Shared.Models.v10.Filters;
using Microsoft.AspNetCore.Http;

public interface IDeviceModelService<TListItem, TModel>
where TListItem : class, IDeviceModel
where TModel : class, IDeviceModel
{
Task<IEnumerable<TListItem>> GetDeviceModels();
Task<PaginatedResult<DeviceModelDto>> GetDeviceModels(DeviceModelFilter deviceModelFilter);

Task<TModel> GetDeviceModel(string deviceModelId);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<MudExpansionPanels>
<MudExpansionPanel Text="Search panel">
<MudGrid>
<MudItem xs="12" md="6">
<MudTextField @bind-Value="searchText" Placeholder="Device Model Name / Device Model Description" id="searchText"></MudTextField>
</MudItem>

<MudItem xs="12">
<MudButton Variant="Variant.Outlined" Color="Color.Success" Style="margin:0.5em;" id="searchButton" OnClick=Search>Search</MudButton>
<MudButton Variant="Variant.Outlined" Color="Color.Primary" Style="margin:0.5em;" OnClick="Reset" id="resetSearch">Reset</MudButton>
</MudItem>

</MudGrid>

</MudExpansionPanel>
</MudExpansionPanels>

@code {
[Parameter]
public EventCallback<DeviceModelSearchInfo> OnSearch { get; set; }

private string? searchText = string.Empty;

private async Task Search()
{
var searchInfo = new DeviceModelSearchInfo
{
SearchText = searchText
};
await OnSearch.InvokeAsync(searchInfo);
}

private async Task Reset()
{
searchText = string.Empty;

await Search();
}
}
10 changes: 10 additions & 0 deletions src/AzureIoTHub.Portal.Client/Models/DeviceModelSearchInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) CGI France. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace AzureIoTHub.Portal.Client.Models
{
public class DeviceModelSearchInfo
{
public string? SearchText { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@
isProcessing = true;

var models = await DeviceModelsClientService.GetDeviceModels();
AvailableModels = models.Where(c => !c.SupportLoRaFeatures).ToArray();

AvailableModels = models.Items.Where(c => !c.SupportLoRaFeatures).ToArray();

AvailableTags = await DeviceTagSettingsClientService.GetDeviceTags();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@page "/device-models"
@using AzureIoTHub.Portal.Models.v10
@using AzureIoTHub.Portal.Shared.Models.v10.Filters;

@attribute [Authorize]
@inject NavigationManager navigationManager
Expand All @@ -9,7 +10,10 @@

<MudGrid>
<MudItem xs="12">
<MudTable T="DeviceModelDto" Items="@result" Loading="@IsLoading" Dense=true Hover=true Bordered=true Striped=true OnRowClick="@((e) => GoToDetails(e.Item))" RowStyle="cursor: pointer;">
<DeviceModelSearch OnSearch=@(async args => await Search(args)) />
</MudItem>
<MudItem xs="12">
<MudTable T="DeviceModelDto" ServerData=@LoadDeviceModels Loading="@IsLoading" @ref="table" Dense=true Hover=true Bordered=true Striped=true OnRowClick="@((e) => GoToDetails(e.Item))" RowStyle="cursor: pointer;">
<ColGroup>
<col style="width: 5%;" />
<col style="width: 30%;" />
Expand All @@ -21,16 +25,16 @@
<MudText Typo="Typo.h6">Device Models</MudText>
<MudSpacer />
<MudTooltip Text="Refresh list">
<MudIconButton Icon="@Icons.Material.Filled.Refresh" Size="Size.Medium" OnClick="LoadDeviceModels" Class="ma-2" id="tableRefreshButton"></MudIconButton>
<MudIconButton Icon="@Icons.Material.Filled.Refresh" Size="Size.Medium" OnClick="@Refresh" Class="ma-2" id="tableRefreshButton"></MudIconButton>
</MudTooltip>
<MudTooltip Text="Add device model">
<MudFab Color="Color.Secondary" Icon="@Icons.Material.Filled.Add" Size="Size.Medium" OnClick="AddDeviceModel" id="addDeviceModelButton" />
</MudTooltip>
</ToolBarContent>
<HeaderContent>
<MudTh></MudTh>
<MudTh><MudTableSortLabel SortLabel="Name" T="DeviceModelDto">Name</MudTableSortLabel></MudTh>
<MudTh Style="text-align: center"><MudTableSortLabel SortLabel="Description" T="DeviceModelDto">Description</MudTableSortLabel></MudTh>
<MudTh><MudTableSortLabel SortLabel="Name" T="DeviceModelDto" id="NameLabel">Name</MudTableSortLabel></MudTh>
<MudTh Style="text-align: center"><MudTableSortLabel SortLabel="Description" T="DeviceModelDto" id="DescriptionLabel">Description</MudTableSortLabel></MudTh>
<MudTh Style="text-align: center">Details</MudTh>
<MudTh Style="text-align: center">Delete</MudTh>
</HeaderContent>
Expand Down Expand Up @@ -63,7 +67,7 @@
</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager PageSizeOptions="@pageSizeOptions"></MudTablePager>
<MudTablePager />
</PagerContent>
<NoRecordsContent>
<MudText>No matching records found</MudText>
Expand All @@ -80,15 +84,20 @@
[CascadingParameter]
public Error Error { get; set; }

private int[] pageSizeOptions = new int[] { 2, 5, 10 };
private List<DeviceModelDto> result = new();
private MudTable<DeviceModelDto> table;

private readonly Dictionary<int, string> pages = new();

//private List<DeviceModelDto> DeviceModelList = new List<DeviceModelDto>();

private bool IsLoading = true;

protected override async Task OnInitializedAsync()
{
await LoadDeviceModels();
IsLoading = false;
}
private DeviceModelSearchInfo deviceModelSearchInfo = new();

//protected override async Task OnInitializedAsync()
//{
// table.ReloadServerData();
//}

private void AddDeviceModel()
{
Expand All @@ -99,16 +108,46 @@
/// Sends a GET request to the DeviceModelsController, to retrieve all device models from the database
/// </summary>
/// <returns></returns>
private async Task LoadDeviceModels()
private async Task<TableData<DeviceModelDto>> LoadDeviceModels(TableState state)
{
try
{
result.Clear();
result.AddRange(await DeviceModelsClientService.GetDeviceModels());
IsLoading = true;

string orderBy = null;

switch (state.SortDirection)
{
case SortDirection.Ascending:
orderBy = $"{state.SortLabel} asc";
break;
case SortDirection.Descending:
orderBy = $"{state.SortLabel} desc";
break;
}

var result = await DeviceModelsClientService.GetDeviceModels(new DeviceModelFilter
{
SearchText = this.deviceModelSearchInfo.SearchText,
PageNumber = state.Page,
PageSize = state.PageSize,
OrderBy = new string[] { orderBy }
});

return new TableData<DeviceModelDto>
{
Items = result.Items,
TotalItems = result.TotalItems
};
}
catch (ProblemDetailsException exception)
{
Error?.ProcessProblemDetails(exception);
return new TableData<DeviceModelDto>();
}
finally
{
IsLoading = false;
}
}

Expand All @@ -125,11 +164,31 @@
}

// Update the list of devices after the deletion
await LoadDeviceModels();
// await LoadDeviceModels();
await Search();
}

private void GoToDetails(DeviceModelDto item)
{
navigationManager.NavigateTo($"/device-models/{item.ModelId}{((item.SupportLoRaFeatures && Portal.IsLoRaSupported) ? "?isLora=true" : "")}");
}

private async Task Search(DeviceModelSearchInfo deviceModelSearchInfo = null)
{
if (deviceModelSearchInfo == null)
{
this.deviceModelSearchInfo = new();
}
else
{
this.deviceModelSearchInfo = deviceModelSearchInfo;
}

await table.ReloadServerData();
}

private async void Refresh()
{
await table.ReloadServerData();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,8 @@
Device.IsEnabled = true;

// Gets a list of device model previously registered to Azure to allow autocomplete field in the form
DeviceModelList = await DeviceModelsClientService.GetDeviceModels();
var result = await DeviceModelsClientService.GetDeviceModels();
DeviceModelList = result.Items.ToList<IDeviceModel>();

// Gets the custom tags that can be set when creating a device
TagList = await DeviceTagSettingsClientService.GetDeviceTags();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@
{
try
{
this.ModelList = (await DeviceModelsClientService.GetDeviceModels()).ToList();
this.ModelList = (await DeviceModelsClientService.GetDeviceModels()).Items.ToList();
// Gets the custom tags that can be searched via the panel
TagList = await DeviceTagSettingsClientService.GetDeviceTags();
foreach (var tag in TagList)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,33 @@ namespace AzureIoTHub.Portal.Client.Services
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using AzureIoTHub.Portal.Shared.Models.v10.Filters;
using Microsoft.AspNetCore.WebUtilities;
using Portal.Models.v10;

public class DeviceModelsClientService : IDeviceModelsClientService
{
private readonly HttpClient http;
private readonly string apiUrlBase = "api/models";

public DeviceModelsClientService(HttpClient http)
{
this.http = http;
}

public async Task<IList<DeviceModelDto>> GetDeviceModels()
public async Task<PaginationResult<DeviceModelDto>> GetDeviceModels(DeviceModelFilter? deviceModelFilter = null)
{
return await this.http.GetFromJsonAsync<List<DeviceModelDto>>("api/models");
var query = new Dictionary<string, string>
{
{ nameof(DeviceModelFilter.SearchText), deviceModelFilter?.SearchText ?? string.Empty },
{ nameof(DeviceModelFilter.PageNumber), deviceModelFilter?.PageNumber.ToString() ?? string.Empty },
{ nameof(DeviceModelFilter.PageSize), deviceModelFilter?.PageSize.ToString() ?? string.Empty },
{ nameof(DeviceModelFilter.OrderBy), string.Join("", deviceModelFilter?.OrderBy) ?? string.Empty }
};

var uri = QueryHelpers.AddQueryString(this.apiUrlBase, query);

return await this.http.GetFromJsonAsync<PaginationResult<DeviceModelDto>>(uri);
}

public Task<DeviceModelDto> GetDeviceModel(string deviceModelId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ namespace AzureIoTHub.Portal.Client.Services
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using AzureIoTHub.Portal.Shared.Models.v10.Filters;
using Portal.Models.v10;

public interface IDeviceModelsClientService
{
Task<IList<DeviceModelDto>> GetDeviceModels();
Task<PaginationResult<DeviceModelDto>> GetDeviceModels(DeviceModelFilter? deviceModelFilter = null);

Task<DeviceModelDto> GetDeviceModel(string deviceModelId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@

namespace AzureIoTHub.Portal.Server.Controllers.V10
{
using System.Collections.Generic;
using System.Threading.Tasks;
using AzureIoTHub.Portal.Application.Services;
using AzureIoTHub.Portal.Models.v10;
using AzureIoTHub.Portal.Shared.Models;
using AzureIoTHub.Portal.Shared.Models.v10.Filters;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;

public abstract class DeviceModelsControllerBase<TListItemModel, TModel> : ControllerBase
where TListItemModel : class, IDeviceModel
Expand Down Expand Up @@ -36,9 +38,33 @@ protected DeviceModelsControllerBase(IDeviceModelService<TListItemModel, TModel>
/// Gets the device models.
/// </summary>
/// <returns>The list of device models.</returns>
public virtual async Task<ActionResult<IEnumerable<TListItemModel>>> GetItems()
public virtual async Task<ActionResult<PaginationResult<DeviceModelDto>>> GetItems(DeviceModelFilter deviceModelFilter)
{
return Ok(await this.deviceModelService.GetDeviceModels());
var paginatedDevices = await this.deviceModelService.GetDeviceModels(deviceModelFilter);

var nextPage = string.Empty;

if (paginatedDevices.HasNextPage)
{
nextPage = Url.RouteUrl(new UrlRouteContext
{
RouteName = "GET Device Model list",
Values = new
{
deviceModelFilter.SearchText,
deviceModelFilter.PageSize,
pageNumber = deviceModelFilter.PageNumber + 1,
deviceModelFilter.OrderBy
}
});
}

return new PaginationResult<DeviceModelDto>
{
Items = paginatedDevices.Data,
TotalItems = paginatedDevices.TotalCount,
NextPage = nextPage
};
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

namespace AzureIoTHub.Portal.Server.Controllers.V10
{
using System.Collections.Generic;
using System.Threading.Tasks;
using AzureIoTHub.Portal.Models.v10;
using AzureIoTHub.Portal.Application.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using AzureIoTHub.Portal.Shared.Models.v10.Filters;

[Authorize]
[ApiController]
Expand All @@ -33,9 +33,9 @@ public DeviceModelsController(IDeviceModelService<DeviceModelDto, DeviceModelDto
/// <returns>An array representing the device models.</returns>
[HttpGet(Name = "GET Device model list")]
[ProducesResponseType(StatusCodes.Status200OK)]
public override Task<ActionResult<IEnumerable<DeviceModelDto>>> GetItems()
public override async Task<ActionResult<PaginationResult<DeviceModelDto>>> GetItems([FromQuery] DeviceModelFilter deviceModelFilter)
{
return base.GetItems();
return await base.GetItems(deviceModelFilter);
}

/// <summary>
Expand Down
Loading