From 310de68dff32a4c27cf60f5da3e169382bcb53e9 Mon Sep 17 00:00:00 2001 From: shudsky <67435074+shudsky@users.noreply.github.com> Date: Thu, 20 Feb 2025 16:22:20 -0500 Subject: [PATCH 1/3] Adding filtering, last login date --- .../Pages/Tools/Users/UsersTable.razor | 237 +++++++++++++++--- 1 file changed, 201 insertions(+), 36 deletions(-) diff --git a/Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor b/Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor index b5f456151..b4c28ada9 100644 --- a/Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor +++ b/Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor @@ -10,30 +10,61 @@ @inject IUsersStatusService _userStatusService; @inject DatahubPortalConfiguration _datahubPortalConfiguration @inject IHttpClientFactory _httpClientFactory +@inject IJSRuntime _jsRuntime - - - + + + + + @_allButton + + + + + + + + + + + + + @Localizer["All"] + @foreach (var days in lastXDaysOptions) + { + @Localizer[days] + } + - + @Localizer["User"] @if (!HideInfo) { - - @Localizer["Status"] - + @Localizer["Status"] } @Localizer["Workspaces"] - + + + @Localizer["Last login date"] + + + @@ -43,7 +74,7 @@ @if (!HideInfo) { - @switch(GetUserStatus(@context.User.Email)) + @* @switch(GetUserStatus(@context.User.Email)) { case "locked": @@ -53,7 +84,7 @@ break; case "": break; - } + } *@ } @@ -68,6 +99,9 @@ } + + @context.User.LastLoginDateTime + @@ -81,7 +115,13 @@ [Parameter] public bool HideInfo { get; set; } - private string _searchString; + private MudTable userTable; + + private string _searchString = "", _lastXDays = ""; + private int _numberWorkspaces = 0; + + private string _allButton = "Copy"; + private List _filteredEmails; private string AcronymStyle => new StyleBuilder() .Build(); @@ -92,43 +132,168 @@ public bool LoadStatus = false; + private List lastXDaysOptions = + [ + // last 30 = < 30 days + "Last 7 days", + "Last 30 days", + "Last 90", + "Last 120 days", + "Last 365 days", + "> 1 year" + ]; + protected override async Task OnInitializedAsync() { - var resultDict = await _userStatusService.GetUsersStatus(); - if (resultDict != null) + // var resultDict = await _userStatusService.GetUsersStatus(); + // if (resultDict != null) + // { + // AllUsers = resultDict["all"]; + // LockedUsers = resultDict["locked"]; + // LoadStatus = true; + // } + } + + // private string GetUserStatus(string email) + // { + // var status = ""; + // if (LockedUsers.Contains(email) && !LockedUsers.Any()) + // { + // status = "locked"; + // } else if (!AllUsers.Contains(email) && !AllUsers.Any()) + // { + // status = "missing"; + // } + // return status; + // } + + private async Task> SortUserTable(TableState state, CancellationToken cancellationToken) + { + var filteredData = UserWorkspaces.AsEnumerable(); + _allButton = "Copy"; + + // Apply type filter + // if (selectedType.HasValue) + // { + // filteredData = filteredData.Where(check => check.ResourceType == selectedType.Value); + // } + + // if (statusFilter.Any()) + // { + // filteredData = filteredData.Where(check => statusFilter.Contains(HealthCheckPageUtil.GetDisplayStatus(check))); + // } + + // Apply search filter + if (!string.IsNullOrEmpty(_searchString)) + { + filteredData = filteredData.Where(check => ((check.User.DisplayName?.Contains(_searchString, StringComparison.OrdinalIgnoreCase) ?? false) + || (check.User.Email?.Contains(_searchString, StringComparison.OrdinalIgnoreCase) ?? false) + || check.Workspaces.Any(x => x.Project_Acronym_CD?.Contains(_searchString, StringComparison.OrdinalIgnoreCase) ?? false) + || check.Workspaces.Any(x => x.ProjectName?.Contains(_searchString, StringComparison.OrdinalIgnoreCase) ?? false))); + } + + // Apply # of workspaces filter + if (_numberWorkspaces > 0) { - AllUsers = resultDict["all"]; - LockedUsers = resultDict["locked"]; - LoadStatus = true; + filteredData = filteredData.Where(check => (check.Workspaces.Count() >= _numberWorkspaces)); } + + // Apply last X days filter + if(_lastXDays != null) + { + if (_lastXDays.Contains("7 days")) + { + filteredData = filteredData.Where(check => check.User.LastLoginDateTime.HasValue && check.User.LastLoginDateTime.Value >= DateTime.UtcNow.AddDays(-7) && check.User.LastLoginDateTime.Value <= DateTime.UtcNow); + } + else if (_lastXDays.Contains("30 days")) + { + filteredData = filteredData.Where(check => check.User.LastLoginDateTime.HasValue && check.User.LastLoginDateTime.Value >= DateTime.UtcNow.AddDays(-30) && check.User.LastLoginDateTime.Value <= DateTime.UtcNow); + } + else if (_lastXDays.Contains("90 days")) + { + filteredData = filteredData.Where(check => check.User.LastLoginDateTime.HasValue && check.User.LastLoginDateTime.Value >= DateTime.UtcNow.AddDays(-90) && check.User.LastLoginDateTime.Value <= DateTime.UtcNow); + } + else if (_lastXDays.Contains("120 days")) + { + filteredData = filteredData.Where(check => check.User.LastLoginDateTime.HasValue && check.User.LastLoginDateTime.Value >= DateTime.UtcNow.AddDays(-120) && check.User.LastLoginDateTime.Value <= DateTime.UtcNow); + } + else if (_lastXDays.Contains("365 days")) + { + filteredData = filteredData.Where(check => check.User.LastLoginDateTime.HasValue && check.User.LastLoginDateTime.Value >= DateTime.UtcNow.AddDays(-365) && check.User.LastLoginDateTime.Value <= DateTime.UtcNow); + } + else if (_lastXDays.Contains("year")) + { + filteredData = filteredData.Where(check => check.User.LastLoginDateTime.HasValue && check.User.LastLoginDateTime.Value < DateTime.UtcNow.AddDays(-365) && check.User.LastLoginDateTime.Value <= DateTime.UtcNow); + } + } + + // Apply sorting + if (state.SortDirection != SortDirection.None) + { + Func sortBy = state.SortLabel switch + { + nameof(Datahub.Portal.Pages.Tools.Users.UserWorkspaces.User) => x => x.User.DisplayName, + nameof(Datahub.Portal.Pages.Tools.Users.UserWorkspaces.User.LastLoginDateTime) => x => x.User.LastLoginDateTime, + _ => x => x.User.DisplayName + }; + + filteredData = filteredData.OrderByDirection(state.SortDirection, sortBy); + } + + // Apply pagination + var totalItems = filteredData.Count(); + var pagedData = filteredData + .Skip(state.Page * state.PageSize) + .Take(state.PageSize) + .ToList(); + + _filteredEmails = filteredData.Where(x => x.User.Email?.Contains("@") ?? false).Select(x => x.User.Email).ToList(); + + return await Task.FromResult(new TableData { TotalItems = totalItems, Items = pagedData }); } - private string GetUserStatus(string email) + private async Task SearchStringChanged(string search) { - var status = ""; - if (LockedUsers.Contains(email) && !LockedUsers.Any()) - { - status = "locked"; - } else if (!AllUsers.Contains(email) && !AllUsers.Any()) + _searchString = search; + await ReloadWorkspaceHealthCheckTableAsync(); + } + + private async Task NumberWorkspacesChanged(int num) + { + _numberWorkspaces = num; + await ReloadWorkspaceHealthCheckTableAsync(); + } + + private async Task LastXDaysChanged(string days) + { + _lastXDays = days; + await ReloadWorkspaceHealthCheckTableAsync(); + } + + private async Task ReloadWorkspaceHealthCheckTableAsync() + { + if (userTable is not null) { - status = "missing"; + await userTable.ReloadServerData(); } - return status; } - private bool FilterUserWorkspaces(UserWorkspaces userWorkspaces) + + // Copy emails to clipboard + private async Task CopyUsersToClipboard() + { + _allButton = await CopyToClipboard(_filteredEmails) ? Localizer["Copied!"] : Localizer["Failed to copy"]; + } + + private async Task CopyToClipboard(List emailList) { - if (string.IsNullOrWhiteSpace(_searchString)) - return true; - if (userWorkspaces.User.DisplayName.Contains(_searchString, StringComparison.OrdinalIgnoreCase)) - return true; - // Check that userWorkspaces.User.Email is not null - if (userWorkspaces.User.Email != null) + var result = false; + if (emailList.Any()) { - if (userWorkspaces.User.Email.Contains(_searchString, StringComparison.OrdinalIgnoreCase)) - return true; + var tempstr = String.Join(",\n", emailList); + await _jsRuntime.InvokeVoidAsync("navigator.clipboard.writeText", tempstr); + result = true; } - return userWorkspaces.Workspaces.Any(x => x.Project_Acronym_CD.Contains(_searchString, StringComparison.OrdinalIgnoreCase)) || - userWorkspaces.Workspaces.Any(x => x.ProjectName.Contains(_searchString, StringComparison.OrdinalIgnoreCase)); + return result; } } \ No newline at end of file From 7808e8c99b9913cd65537b1476953532e5cca276 Mon Sep 17 00:00:00 2001 From: shudsky <67435074+shudsky@users.noreply.github.com> Date: Sun, 2 Mar 2025 22:53:45 -0500 Subject: [PATCH 2/3] Adding filtering to the admin Users table --- .../Pages/Tools/Users/UsersTable.razor | 85 +++++++++++++++---- .../Pages/Tools/Users/UsersTable.razor.js | 11 +++ 2 files changed, 79 insertions(+), 17 deletions(-) create mode 100644 Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor.js diff --git a/Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor b/Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor index b4c28ada9..08fb038f8 100644 --- a/Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor +++ b/Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor @@ -1,3 +1,4 @@ +@using Datahub.Core.Model.Projects @using MudBlazor.Utilities @using System.Text.Json.Nodes; @using Datahub.Application.Services @@ -19,13 +20,17 @@ @_allButton + + + @Localizer["Download to CSV"] + + + - + Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-4" Label="@Localizer["Search"]"> - - @if (!HideInfo) + @* @if (!HideInfo) { @Localizer["Status"] - } + } *@ @Localizer["Workspaces"] @@ -71,10 +76,10 @@ @context.User.DisplayName @context.User.Email - @if (!HideInfo) + @* @if (!HideInfo) { - @* @switch(GetUserStatus(@context.User.Email)) + @switch(GetUserStatus(@context.User.Email)) { case "locked": @@ -84,9 +89,9 @@ break; case "": break; - } *@ + } - } + } *@
    @foreach(var workspace in context.Workspaces) @@ -112,32 +117,33 @@ [Parameter] public ICollection UserWorkspaces { get; set; } + public IEnumerable _filteredData { get; set; } + [Parameter] public bool HideInfo { get; set; } + private IJSObjectReference _module; + private MudTable userTable; private string _searchString = "", _lastXDays = ""; private int _numberWorkspaces = 0; - private string _allButton = "Copy"; + private string _allButton = "Copy emails"; private List _filteredEmails; private string AcronymStyle => new StyleBuilder() .Build(); public List LockedUsers = new (); - public List AllUsers = new (); - public bool LoadStatus = false; private List lastXDaysOptions = [ - // last 30 = < 30 days "Last 7 days", "Last 30 days", - "Last 90", + "Last 90 days", "Last 120 days", "Last 365 days", "> 1 year" @@ -145,6 +151,7 @@ protected override async Task OnInitializedAsync() { + _module = await _jsRuntime.InvokeAsync("import", "./Pages/Tools/Users/UsersTable.razor.js"); // var resultDict = await _userStatusService.GetUsersStatus(); // if (resultDict != null) // { @@ -170,7 +177,7 @@ private async Task> SortUserTable(TableState state, CancellationToken cancellationToken) { var filteredData = UserWorkspaces.AsEnumerable(); - _allButton = "Copy"; + _allButton = "Copy emails"; // Apply type filter // if (selectedType.HasValue) @@ -195,8 +202,9 @@ // Apply # of workspaces filter if (_numberWorkspaces > 0) { - filteredData = filteredData.Where(check => (check.Workspaces.Count() >= _numberWorkspaces)); + filteredData = filteredData.Where(check => check.Workspaces.Count() >= _numberWorkspaces); } + Console.WriteLine(_numberWorkspaces); // Apply last X days filter if(_lastXDays != null) @@ -248,6 +256,7 @@ .ToList(); _filteredEmails = filteredData.Where(x => x.User.Email?.Contains("@") ?? false).Select(x => x.User.Email).ToList(); + _filteredData = filteredData; return await Task.FromResult(new TableData { TotalItems = totalItems, Items = pagedData }); } @@ -260,10 +269,11 @@ private async Task NumberWorkspacesChanged(int num) { + Console.WriteLine(_numberWorkspaces); _numberWorkspaces = num; await ReloadWorkspaceHealthCheckTableAsync(); } - + private async Task LastXDaysChanged(string days) { _lastXDays = days; @@ -296,4 +306,45 @@ } return result; } + + // CSV download + private async Task HandleDownload() + { + var fileStream = GenerateCsvStream(); + var fileName = $"UserInfo_{DateTime.Now.ToString("yyyy-MM-dd")}.csv"; + using var streamReference = new DotNetStreamReference(stream: fileStream); + await _module.InvokeVoidAsync("downloadFileFromStream", fileName, streamReference); + } + + private Stream GenerateCsvStream() + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.WriteLine("Name,Email,Workspaces,Last Login"); + foreach (var cost in _filteredData) + { + writer.WriteLine($"\"{cost.User.DisplayName}\",\"{cost.User.Email}\",\"{AllWorkspaces(cost.Workspaces)}\",{cost.User.LastLoginDateTime?.ToString("g", CultureInfo.CreateSpecificCulture("en-CA"))}"); + } + + writer.Flush(); + stream.Position = 0; + return stream; + } + + private string AllWorkspaces(List workspaces) + { + string allWorkspaces = ""; + foreach(var space in workspaces) + { + if(space.Equals(workspaces.Last())) + { + allWorkspaces += space.ProjectName + "(" + space.Project_Acronym_CD + ")"; + } + else + { + allWorkspaces += space.ProjectName + "(" + space.Project_Acronym_CD + "), "; + } + } + return allWorkspaces; + } } \ No newline at end of file diff --git a/Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor.js b/Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor.js new file mode 100644 index 000000000..6d992b824 --- /dev/null +++ b/Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor.js @@ -0,0 +1,11 @@ +export async function downloadFileFromStream(fileName, contentStreamReference) { + const arrayBuffer = await contentStreamReference.arrayBuffer(); + const blob = new Blob([arrayBuffer]); + const url = URL.createObjectURL(blob); + const anchorElement = document.createElement('a'); + anchorElement.href = url; + anchorElement.download = fileName ?? ''; + anchorElement.click(); + anchorElement.remove(); + URL.revokeObjectURL(url); +} \ No newline at end of file From 1082c48ae9dca36524cf4aae6d80073e9e20ac1d Mon Sep 17 00:00:00 2001 From: shudsky <67435074+shudsky@users.noreply.github.com> Date: Sun, 2 Mar 2025 22:59:52 -0500 Subject: [PATCH 3/3] Removed comments --- .../Pages/Tools/Users/UsersTable.razor | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor b/Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor index 08fb038f8..99d1291b7 100644 --- a/Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor +++ b/Portal/src/Datahub.Portal/Pages/Tools/Users/UsersTable.razor @@ -55,12 +55,6 @@ @Localizer["User"] - @* @if (!HideInfo) - { - - @Localizer["Status"] - - } *@ @Localizer["Workspaces"] @@ -76,22 +70,6 @@ @context.User.DisplayName @context.User.Email - @* @if (!HideInfo) - { - - @switch(GetUserStatus(@context.User.Email)) - { - case "locked": - - break; - case "missing": - - break; - case "": - break; - } - - } *@
      @foreach(var workspace in context.Workspaces) @@ -152,44 +130,13 @@ protected override async Task OnInitializedAsync() { _module = await _jsRuntime.InvokeAsync("import", "./Pages/Tools/Users/UsersTable.razor.js"); - // var resultDict = await _userStatusService.GetUsersStatus(); - // if (resultDict != null) - // { - // AllUsers = resultDict["all"]; - // LockedUsers = resultDict["locked"]; - // LoadStatus = true; - // } } - // private string GetUserStatus(string email) - // { - // var status = ""; - // if (LockedUsers.Contains(email) && !LockedUsers.Any()) - // { - // status = "locked"; - // } else if (!AllUsers.Contains(email) && !AllUsers.Any()) - // { - // status = "missing"; - // } - // return status; - // } - private async Task> SortUserTable(TableState state, CancellationToken cancellationToken) { var filteredData = UserWorkspaces.AsEnumerable(); _allButton = "Copy emails"; - // Apply type filter - // if (selectedType.HasValue) - // { - // filteredData = filteredData.Where(check => check.ResourceType == selectedType.Value); - // } - - // if (statusFilter.Any()) - // { - // filteredData = filteredData.Where(check => statusFilter.Contains(HealthCheckPageUtil.GetDisplayStatus(check))); - // } - // Apply search filter if (!string.IsNullOrEmpty(_searchString)) {